AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

PixelColor(x, y, window): transp., off-screen etc. windows
Goto page 1, 2  Next
 
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Mon Mar 09, 2009 5:27 pm    Post subject: PixelColor(x, y, window): transp., off-screen etc. windows Reply with quote

AutoHotkey.chm: PixelGetColor wrote:
The pixel must be visible; in other words, it is not possible to retrieve the pixel color of a window hidden behind another window.
...
A window that is partially transparent or that has one of its colors marked invisible (TransColor) typically yields colors for the window behind itself rather than its own.

To retrieve the pixel color from a window, which is out of screen (fully or partially), transparent/translucent, or behind (an)other window(s), use this function:

Code:
PixelColor(pc_x, pc_y, pc_wID)
{
   If pc_wID
   {
      pc_hDC := DllCall("GetDC", "UInt", pc_wID)
      WinGetPos, , , pc_w, pc_h, ahk_id %pc_wID%
      pc_hCDC := CreateCompatibleDC(pc_hDC)
      pc_hBmp := CreateCompatibleBitmap(pc_hDC, pc_w, pc_h)
      pc_hObj := SelectObject(pc_hCDC, pc_hBmp)
      
      pc_hmCDC := CreateCompatibleDC(pc_hDC)
      pc_hmBmp := CreateCompatibleBitmap(pc_hDC, 1, 1)
      pc_hmObj := SelectObject(pc_hmCDC, pc_hmBmp)
      
      DllCall("PrintWindow", "UInt", pc_wID, "UInt", pc_hCDC, "UInt", 0)
      DllCall("BitBlt" , "UInt", pc_hmCDC, "Int", 0, "Int", 0, "Int", 1, "Int", 1, "UInt", pc_hCDC, "Int", pc_x, "Int", pc_y, "UInt", 0xCC0020)
      pc_fmtI := A_FormatInteger
      SetFormat, Integer, Hex
      DllCall("GetBitmapBits", "UInt", pc_hmBmp, "UInt", VarSetCapacity(pc_bits, 4, 0), "UInt", &pc_bits)
      pc_c := NumGet(pc_bits, 0)
      SetFormat, Integer, %pc_fmtI%

      DeleteObject(pc_hBmp), DeleteObject(pc_hmBmp)
      DeleteDC(pc_hCDC), DeleteDC(pc_hmCDC)
      DllCall("ReleaseDC", "UInt", pc_wID, "UInt", pc_hDC)
      Return pc_c
   }
}


CreateCompatibleDC(hdc=0) {
   return DllCall("CreateCompatibleDC", "UInt", hdc)
}     

CreateCompatibleBitmap(hdc, w, h) {
   return DllCall("CreateCompatibleBitmap", UInt, hdc, Int, w, Int, w)
}

SelectObject(hdc, hgdiobj) {
   return DllCall("SelectObject", "UInt", hdc, "UInt", hgdiobj)
}

DeleteObject(hObject) {
   Return, DllCall("DeleteObject", "UInt", hObject)
}

DeleteDC(hdc) {
   Return, DllCall("DeleteDC", "UInt", hdc)
}


It's based on infogulch's RegionGetColor

As expected, won't work on minimized windows, or area "out of scroll".
Also you can't get the color of a video (overlayed or not), and using this function on a video application window brings the video control on top, and the video 'twitchs' (I want to say: stops for a moment) when this function is called (maybe not an issue if you've got a "supercomputer" Very Happy).

Scrolling a window which is monitored by this function may result in artifacts (at least IE).

Video and scroll problems are same as with LiveWindows.

Memory leak -tested.

Variables used by the script (preceeded by pc_ to make it simple to insert it to scripts as non-function) are:
Code:
pc_bits
pc_c
pc_fmtI
pc_h
pc_hBmp
pc_hCDC
pc_hDC
pc_hmBmp
pc_hmCDC
pc_hmObj
pc_hObj
pc_w
pc_wID
pc_x
pc_y



I used the following script to verify that the function is working.
It shows how you can get pixel color using a loop (more efficient without creating + deleting/releasing DCs, bitmaps ans selecting/deleting objects each time). Also a timer could be employed to call those 3 dll functions.
The value shown by the tooltip should change almost every second: digits 5 and 6 vs. 0 and 9 have same pixel at that position with default(?) font (seems to be Tahoma), normal size (8 points).
Esc to exit.

Code:
Run timedate.cpl
WinWaitActive ahk_class #32770
Loop
{
   WinGet clock?, ControlList, A
   IfInString clock?, ClockWndMain1
      Break
   Sleep 100
}
SendMessage, 0x1330, 0, , SysTabControl321 ; Select first tab

ControlGetPos, pc_x, pc_y, , , Edit5 ; Edit5 = seconds
pc_x += 8, pc_y +=6
pc_wID := WinExist()

   If pc_wID
   {
      pc_hDC := DllCall("GetDC", "UInt", pc_wID)
      WinGetPos, , , pc_w, pc_h, ahk_id %pc_wID%
      pc_hCDC := CreateCompatibleDC(pc_hDC)
      pc_hBmp := CreateCompatibleBitmap(pc_hDC, pc_w, pc_h)
      pc_hObj := SelectObject(pc_hCDC, pc_hBmp)
      
      pc_hmCDC := CreateCompatibleDC(pc_hDC)
      pc_hmBmp := CreateCompatibleBitmap(pc_hDC, 1, 1)
      pc_hmObj := SelectObject(pc_hmCDC, pc_hmBmp)
      
      Loop
      {
         DllCall("PrintWindow", "UInt", pc_wID, "UInt", pc_hCDC, "UInt", 0)
         DllCall("BitBlt" , "UInt", pc_hmCDC, "Int", 0, "Int", 0, "Int", 1, "Int", 1, "UInt", pc_hCDC, "Int"
         , pc_x, "Int", pc_y, "UInt", 0xCC0020)
         pc_fmtI := A_FormatInteger
         SetFormat, Integer, Hex
         DllCall("GetBitmapBits", "UInt", pc_hmBmp, "UInt", VarSetCapacity(pc_bits, 4, 0), "UInt", &pc_bits)
         pc_c := NumGet(pc_bits, 0)
         ToolTip % pc_c
         SetFormat, Integer, %pc_fmtI%
         Sleep 500
      }
      Esc::
      DeleteObject(pc_hBmp), DeleteObject(pc_hmBmp)
      DeleteDC(pc_hCDC), DeleteDC(pc_hmCDC)
      DllCall("ReleaseDC", "UInt", pc_wID, "UInt", pc_hDC)
         ExitApp
   }


CreateCompatibleDC(hdc=0) {
   return DllCall("CreateCompatibleDC", "UInt", hdc)
}     

CreateCompatibleBitmap(hdc, w, h) {
   return DllCall("CreateCompatibleBitmap", UInt, hdc, Int, w, Int, w)
}

SelectObject(hdc, hgdiobj) {
   return DllCall("SelectObject", "UInt", hdc, "UInt", hgdiobj)
}

DeleteObject(hObject) {
   Return, DllCall("DeleteObject", "UInt", hObject)
}

DeleteDC(hdc) {
   Return, DllCall("DeleteDC", "UInt", hdc)
}

_________________
Pekka Vartto


Last edited by svi on Wed Mar 18, 2009 3:42 pm; edited 4 times in total
Back to top
View user's profile Send private message
users
Guest





PostPosted: Tue Mar 10, 2009 8:55 am    Post subject: Reply with quote

can you make it do an image file in the var and find image?
Back to top
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Tue Mar 10, 2009 4:16 pm    Post subject: Reply with quote

Sounds difficult for me.
I'm not aware of such functions (dll), so the only way I know at the moment is to do it byte by byte (very slow).

But probably I didn't understand you fully. Do you want to "save" an image into the memory (convert it to a variable), and do an imagesearch?
_________________
Pekka Vartto
Back to top
View user's profile Send private message
RTFM
Guest





PostPosted: Tue Mar 10, 2009 4:29 pm    Post subject: Reply with quote

Quote:
can you make it do an image file in the var and find image?
I guess his (or her) main focus is to identify an image (a specific pattern of pixels) on a hidden/virtual area of the screen. Specifically gamesters looking for that option, to ran multiple instances of a game (pushed to the virtual area of the screen) and be able to trigger those even they are 'out of sight'.
Back to top
users
Guest





PostPosted: Wed Mar 11, 2009 12:29 am    Post subject: Reply with quote

the goal would be better imagesearch rather than reading from a file everytime for loops
Back to top
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Fri Mar 13, 2009 5:21 pm    Post subject: Reply with quote

It seems "expensive" to read the image for every search, but it's been said that it isn't probably repeatedly loaded from the disk (because of caching).

I tried to verify it, and got results that are in favor for that (I hope this is a valid expression).

From the picture you see the peak that reflects opening of an 1 kB image file for the first time. The second time it's opened isn't seen on the highlighted graph, which represents (word by word translation) "Aver. disk bytes/read, Physical disk".
Running continuous ImageSearch doesn't show any disk read activity.

_________________
Pekka Vartto
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Mon Mar 16, 2009 9:53 am    Post subject: Reply with quote

Your function seems somewhat overcomplicated to me, perhaps because it is based on RegionGetColor. There is a much simpler way, which works only on the client area of the window (excludes the window frame):
Code:
PixelColorSimple(pc_x, pc_y, pc_wID)
{
    if pc_wID
    {
        pc_hDC := DllCall("GetDC", "UInt", pc_wID)
        pc_fmtI := A_FormatInteger
        SetFormat, IntegerFast, Hex
        pc_c := DllCall("GetPixel", "UInt", pc_hDC, "Int", pc_x, "Int", pc_y, "UInt")
        pc_c := pc_c >> 16 & 0xff | pc_c & 0xff00 | (pc_c & 0xff) << 16
        pc_c .= ""
        SetFormat, IntegerFast, %pc_fmtI%
        DllCall("ReleaseDC", "UInt", pc_wID, "UInt", pc_hDC)
        return pc_c
    }
}
This method directly gets the wanted pixel from the image data of the window, rather than printing the window onto a temporary bitmap, bitblitting the bitmap onto another bitmap and getting the final bitmap's bits. It should be much more efficient, with less undesirable side-effects.

IntegerFast is a feature of AutoHotkey v1.0.48 which allows the format to be changed without disabling binary number caching ( another feature of v1.0.48 ). With the fast mode, pc_c .= "" is necessary to force the number into a string of the current format.

For consistency with the other function, it swaps the red and blue bytes. It also filters out alpha data (pc_c & 0xff000000), which I think may be present on some windows in Vista+ with Aero enabled.)
Back to top
View user's profile Send private message Visit poster's website
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Tue Mar 17, 2009 8:39 pm    Post subject: Reply with quote

Thanks for your response, Lexikos,

but I think you should have read more carefully / I should have stated in the subject that the function is for pixels that can't be retrieved by PixelGetColor (or GetPixel), but I wanted to keep the title simple / there's no much room (I've edited it now).
My function is complicated and slow (depends on window size?), but the only way seen so far.

Why there's an extra "Uint" at the end:
Code:
pc_c := DllCall("GetPixel", "UInt", pc_hDC, "Int", pc_x, "Int", pc_y, "UInt")


So, the last AHK version is 1.0.4Cool
(Funny effects, when smileys are on.)

P.S.
I appreciate all feedback, though my language may sound rude (lacking skills Embarassed).
_________________
Pekka Vartto
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Tue Mar 17, 2009 9:54 pm    Post subject: Reply with quote

svi wrote:
but I think you should have read more carefully / I should have stated in the subject that the function is for pixels that can't be retrieved by PixelGetColor (or GetPixel),
It may be misleading, because using PrintWindow means you are getting pixels that aren't really there, and the ones that are may not match what the user sees. Anyhow, I forgot about older versions of Windows/Vista with Aero disabled. With Aero (or layered windows on Win2k+), off-screen pixels are available to GetPixel.
Quote:
My function is complicated and slow (depends on window size?), but the only way seen so far.
I still think it's overcomplicated. Rather than printing onto one bitmap, blitting onto another, and getting the final bitmap's bits, you could simply print onto one bitmap and use GetPixel on it.
Quote:
Why there's an extra "Uint" at the end:
GetPixel returns an unsigned value. DllCall defaults to signed. It doesn't matter in this case because I used bitwise & to "fix" the value.
Back to top
View user's profile Send private message Visit poster's website
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Wed Mar 18, 2009 1:22 am    Post subject: Reply with quote

I'm sorry, I was too sure...
I tested the PixelColorSimple carefully, and as it didn't work when the window was out of screen or behind (an)other window(s), I thought it wouldn't work on any system.

Lexikos wrote:
GetPixel returns an unsigned value. DllCall defaults to signed.
It's documented, but so rarely used, that I've regarded as an error Rolling Eyes

I would have never guessed that GetPixel works on virtual windows, so thanks to Lexikos I've shortened the scripts remarkably [requires v1.0.48].

Function:
Code:
PixelColor(pc_x, pc_y, pc_wID)
{   
   If pc_wID
   {
      pc_hDC := DllCall("GetDC", "UInt", pc_wID)
      WinGetPos, , , pc_w, pc_h, ahk_id %pc_wID%
      pc_hCDC := DllCall("CreateCompatibleDC", "UInt", pc_hDC)
      pc_hBmp := DllCall("CreateCompatibleBitmap", "UInt", pc_hDC, "Int", pc_w, "Int", pc_h)
      pc_hObj := DllCall("SelectObject", "UInt", pc_hCDC, "UInt", pc_hBmp)
      DllCall("PrintWindow", "UInt", pc_wID, "UInt", pc_hCDC, "UInt", 0)
      pc_fmtI := A_FormatInteger
      SetFormat, IntegerFast, Hex
      pc_c := DllCall("GetPixel", "UInt", pc_hCDC, "Int", pc_x, "Int", pc_y, "UInt")
      pc_c := pc_c >> 16 & 0xff | pc_c & 0xff00 | (pc_c & 0xff) << 16
      pc_c .= ""
      SetFormat, IntegerFast, %pc_fmtI%
      DllCall("DeleteObject", "UInt", pc_hBmp)
      DllCall("DeleteDC", "UInt", pc_hCDC)
      DllCall("ReleaseDC", "UInt", pc_wID, "UInt", pc_hDC)
      Return pc_c
   }
}


Used variables:
Code:
pc_c
pc_fmtI
pc_h
pc_hBmp
pc_hCDC
pc_hDC
pc_hObj
pc_w
pc_wID
pc_x
pc_y



Demo:
Code:
Run timedate.cpl
WinWaitActive ahk_class #32770
Loop
{
   WinGet clock?, ControlList, A
   IfInString clock?, ClockWndMain1
   Break
   Sleep 100
}
SendMessage, 0x1330, 0, , SysTabControl321 ; Select first tab

ControlGetPos, pc_x, pc_y, , , Edit5 ; Edit5 = seconds
pc_x += 8, pc_y += 6
pc_wID := WinExist()

If pc_wID
{
   pc_hDC := DllCall("GetDC", "UInt", pc_wID)
   WinGetPos, , , pc_w, pc_h, ahk_id %pc_wID%
   pc_hCDC := DllCall("CreateCompatibleDC", "UInt", pc_hDC)
   pc_hBmp := DllCall("CreateCompatibleBitmap", "UInt", pc_hDC, "Int", pc_w, "Int", pc_h)
   pc_hObj := DllCall("SelectObject", "UInt", pc_hCDC, "UInt", pc_hBmp)
   Loop
   {
      DllCall("PrintWindow", "UInt", pc_wID, "UInt", pc_hCDC, "UInt", 0)
      pc_fmtI := A_FormatInteger
      SetFormat, IntegerFast, Hex
      pc_c := DllCall("GetPixel", "UInt", pc_hCDC, "Int", pc_x, "Int", pc_y, "UInt")
      pc_c := pc_c >> 16 & 0xff | pc_c & 0xff00 | (pc_c & 0xff) << 16
      pc_c .= ""
      ToolTip % pc_c
      SetFormat, IntegerFast, %pc_fmtI%
      Sleep 500
   }
   Esc::
   DllCall("DeleteObject", "UInt", pc_hBmp)
   DllCall("DeleteDC", "UInt", pc_hCDC)
   DllCall("ReleaseDC", "UInt", pc_wID, "UInt", pc_hDC)
   ExitApp
}

Also "memory leak" -tested.


There were also errors in my original scripts, which didn't affect on the result: Int and Uint were unquoted in CreateCompatibleBitmap function, which made them blank variables.
_________________
Pekka Vartto


Last edited by svi on Wed Mar 18, 2009 1:15 pm; edited 1 time in total
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Wed Mar 18, 2009 8:07 am    Post subject: Reply with quote

svi wrote:
Function (DOESN'T WORK YET!):
What does it do? It works on my end (Windows 7 beta with Aero enabled).
Quote:
There were also errors in my original scripts, which didn't affect on the result: Int and Uint were unquoted in CreateCompatibleBitmap function, which made them blank variables.
I like the saying "I thought I was wrong, but I was mistaken." Laughing
Quote:
Source: AutoHotkey Documentation: DllCall
Note: When specifying an argument type or return type that does not contain a space or asterisk, the quotes around it may be omitted.
DllCall does this by falling back to the name of the variable if its value is not a valid type. In other words, an unquoted type name may cause an empty variable to be created, but it will work.

Btw, DebugBIF is a very useful tool for detecting actual errors with DllCall usage (or other Built-In Functions).
Back to top
View user's profile Send private message Visit poster's website
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Wed Mar 18, 2009 1:40 pm    Post subject: Reply with quote

The function seems to work. I couldn't verify it last night, and was too tired to do nothing than add that sentence in brackets (it was 4:22 am).
I should read more AutoHotkey documentation (as many of us), but for me it's little slow because it's in English Laughing
Now I remember I've read about quoted/unquoted argument types, but had forgotten that.

Quote:
DllCall does this by falling back to the name of the variable if its value is not a valid type
Sometimes testing instead of mastering the proper way gives interesting results:
This simple script that inverts screen colors works although I used arguments dummy and "" instead of those below on the continuing line.
Code:
hDC := DllCall("GetDC")
DllCall("BitBlt", dummy, hDC, "", 0, "int", 0, "int", A_ScreenWidth, "int", A_ScreenHeight
                , "Uint", 0, "int", 0, "int", 0, "Uint", 0x00550009)
DllCall("ReleaseDC", "Uint", 0, "Uint", hDC)

_________________
Pekka Vartto
Back to top
View user's profile Send private message
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Sun Apr 05, 2009 9:07 pm    Post subject: Reply with quote

Using PrintWindow to get the pixel color from "abnormal" window is slow and so unreliable (http://www.autohotkey.com/forum/topic13001.html#81338, by Holomind Sun Oct 01, 2006 12:26 pm) on some windows, that it should be called several times and get the most common value.
It also causes "artifacs" on other windows (try to resize Notepad++, when it's monitored by LiveWindows,

So, if GetPixel works on your system, use it.

This script tests if GetPixel works (utilizes Include a bitmap in your uncompiled script!!! by Veovis):
Code:
color1 = 0x0
color2 = 0xff
color3 = 0xffff00

picture =
( join
424d5a00000000000000360000002800000003000000030000000100180000000000240000000000000000000
0000000000000000000ffffffffffff00ffff000000ffffffff0000ffffff000000000000ffffffffffff000000
)
WriteFile("line.bmp",picture)
Gui, Add, Pic, X0 Y0, line.bmp
Gui, Show, , unique name
pc_wID := WinExist("unique name")

Gosub 3pix
If ind < 3
   MsgBox Doesn't work on normal windows!
Else
   MsgBox Works on normal windows.

WinMove unique name, , -20, -20
Gosub 3pix
If ind < 3
   MsgBox Doesn't work on off-screen windows.
Else
   MsgBox Works on off-screen windows.

Run Notepad, , Max, pid
WinMove unique name, , 0, 0
WinWaitActive ahk_pid %pid%
Gosub 3pix
WinClose ahk_pid %pid%
If ind < 3
   MsgBox Doesn't work on windows behind others.
Else
   MsgBox Works on windows behind others.

WinSet, Transparent, 150, unique name
Gosub 3pix
If ind < 3
   MsgBox Doesn't work on (semi-)transparent windows.
Else
   MsgBox Works on (semi-)transparent windows.
ExitApp

WriteFile(file,data)
{
   Handle :=  DllCall("CreateFile","str",file,"Uint",0x40000000 ; GENERIC_WRITE := 0x40000000
                  ,"Uint",0,"UInt",0,"UInt",4,"Uint",0,"UInt",0)
   Loop
   {
     If StrLen(data) = 0
        Break
     StringLeft, Hex, data, 2         
     StringTrimLeft, data, data, 2
     Hex = 0x%Hex%
     DllCall("WriteFile","UInt", Handle,"UChar *", Hex
     ,"UInt",1,"UInt *",UnusedVariable,"UInt",0)
    }
 
   DllCall("CloseHandle", "Uint", Handle)
   return
}

3pix:
Loop 4
{
   ind := A_Index - 1
   pc_hDC := DllCall("GetDC", "UInt", pc_wID)
   pc_fmtI := A_FormatInteger
   SetFormat, IntegerFast, Hex
   pc_c := DllCall("GetPixel", "UInt", pc_hDC, "Int", ind, "Int", ind, "UInt")
   pc_c := pc_c >> 16 & 0xff | pc_c & 0xff00 | (pc_c & 0xff) << 16
   pc_c .= ""
   SetFormat, IntegerFast, %pc_fmtI%
   DllCall("ReleaseDC", "UInt", pc_wID, "UInt", pc_hDC)
   If (pc_c <> color%A_Index%) ;  SubStr("000000ff000000ffff", (A_Index - 1) * 6 + 1, 6)
      Break
}
Return

_________________
Pekka Vartto
Back to top
View user's profile Send private message
guest3456
Guest





PostPosted: Thu Mar 11, 2010 4:36 pm    Post subject: Reply with quote

Lexikos wrote:
Anyhow, I forgot about older versions of Windows/Vista with Aero disabled. With Aero (or layered windows on Win2k+), off-screen pixels are available to GetPixel.


i was interested in this thread because PrintWindow is so slow, so if i could use GetPixel directly, it would be great.

though, heres a problem with this, according to this site:
http://social.msdn.microsoft.com/Forums/en-US/windowsuidevelopment/thread/4db649d2-4747-4773-8f9a-9b74dacc5a7d

Quote:

"With Aero enabled, Windows doesn't need to maintain an actual bitmap of the display. It simply maintains polygons that hold individual window contents and lets the compositor handle mixing them in the correct way. As soon as you make a call to GetPixel, it is forced into rendering all the windows into a single bitmap so that it can figure out what colour that pixel is. Doing this is always going to be very, very slow in comparison."
Back to top
guest3456
Guest





PostPosted: Thu Mar 11, 2010 4:41 pm    Post subject: Reply with quote

and using the test script above:


my XP machine:

Works on normal windows.
Doesn't work on off-screen windows.
Doesn't work on windows behind others.
Doesn't work on (semi-)transparent windows.


friends Win 7 Aero OFF:

Works on normal windows.
Doesn't work on off-screen windows.
Works on windows behind others.
Doesn't work on (semi-)transparent windows.


friend Win 7 Aero ON:

Works on normal windows.
Works on off-screen windows.
Works on windows behind others.
Works on (semi-)transparent windows.
Back to top
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group