 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Lexikos
Joined: 17 Oct 2006 Posts: 7293 Location: Australia
|
Posted: Mon Dec 28, 2009 11:07 am Post subject: [AHK_L] BinObject - store binary data in objects |
|
|
BinObject()
Requires AutoHotkey_L or a compatible build. If the usage isn't obvious, see the example below.
| Code: | ; Creates an object which facilitates storage of binary data.
BinObject()
{
static BinObjectType
; Initialize base object, once only.
if !BinObjectType
BinObjectType
:= Object("__get" , "BinObject_Get"
, "__set" , "BinObject_Set"
, "__delete", "BinObject_Delete")
; Construct new object.
return Object("_data" , Object() ; Array of pointers.
, "_names" , "," ; List of field names.
, "base" , BinObjectType)
}
BinObject_Get(obj, field, prm1="")
{
if ptr := obj._data[field]
; Return current address of this data field.
return ptr
if field = BinSize
; Return size of specified data field.
return BinObject_GetCapacity(obj, prm1)
}
BinObject_Set(obj, field, prm1, value)
{
if field = BinSize
; Update size of specified data field.
return BinObject_SetCapacity(obj, prm1, value)
}
BinObject_Delete(obj)
{
; Remove leading and trailing comma.
_names := SubStr(obj._names, 2, -1)
; Free each data field.
Loop, Parse, _names, `,
DllCall("GlobalFree", "uint", obj._data[A_LoopField])
}
BinObject_GetCapacity(obj, field)
{
if ptr := obj._data[field]
return DllCall("GlobalSize", "uint", ptr)
}
BinObject_SetCapacity(obj, field, capacity)
{
_data := obj._data ; For performance.
if capacity < 0 ; For possible future use.
return
ptr := _data[field]
if capacity
{ ; Allocate or reallocate this field, if necessary.
if ! existing_ptr := ptr
ptr := DllCall("GlobalAlloc", "uint", 0x40, "uint", capacity)
else if DllCall("GlobalSize", "uint", ptr) != capacity
ptr := DllCall("GlobalReAlloc", "uint", ptr, "uint", capacity, "uint", 0x42)
; Check for failure before updating object.
if !ptr
return
; Update pointer in internal array.
_data[field] := ptr
if !existing_ptr ; Add new field to the list.
obj._names := obj._names . field . ","
return DllCall("GlobalSize", "uint", ptr)
}
else if ptr
{ ; Remove and free this field.
_names := obj._names
StringReplace, _names, _names, `,%field%`,, `,
obj._names := _names
_data._Remove(field)
DllCall("GlobalFree", "uint", ptr)
}
} |
Example
| Code: | ; Create a new bin to throw data in.
obj := BinObject()
; Allocate two fields.
obj.BinSize["x"] := 100
obj.BinSize["y"] := 100
; Store a value in one to show resizing preserves data.
NumPut(123, obj.x)
; Show their current sizes and addresses.
gosub ShowStuff
; Oops, they were too small.
obj.BinSize["x"] := 256
obj.BinSize["y"] := 512
; Show their new sizes and addresses, plus the value stored earlier.
gosub ShowStuff
; Destroy one field.
obj.BinSize["y"] := 0
gosub ShowStuff
ExitApp
ShowStuff:
MsgBox % "x: " obj.BinSize["x"] " " obj.x
. "`ny: " obj.BinSize["y"] " " obj.y
. "`nx= " NumGet(obj.x)
return |
Epilogue
I wrote this script mostly to get a feel for how much work it is without built-in functionality. See relevant discussion. With the methods available in my development build prior to public object support, it could've looked like this (with no dependancy on BinObject()):
| Code: | ; Create a new bin to throw data in.
obj := Object()
; Allocate two fields.
obj._SetCapacity("x", 100)
obj._SetCapacity("y", 100)
; Store a value in one to show resizing preserves data.
NumPut(123, obj._GetAddress("x"))
; Show their current sizes and addresses.
gosub ShowStuff
; Oops, they were too small.
obj._SetCapacity("x", 256)
obj._SetCapacity("y", 512)
; Show their new sizes and addresses, plus the value stored earlier.
gosub ShowStuff
; Destroy one field.
obj._Remove("y")
gosub ShowStuff
ExitApp
ShowStuff:
MsgBox % "x: " obj._GetCapacity("x") " " obj._GetAddress("x")
. "`ny: " obj._GetCapacity("y") " " obj._GetAddress("y")
. "`nx= " NumGet(obj._GetAddress("x"))
return
| I will probably re-enable this functionality in the next release. Note that it would then be possible to rewrite BinObject() to support the same syntax as the first example above, with better performance and less (script) code. |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 4511 Location: Belgrade
|
Posted: Mon Dec 28, 2009 11:56 am Post subject: |
|
|
Great. Thank you. _________________
 |
|
| Back to top |
|
 |
tinku99
Joined: 03 Aug 2007 Posts: 513 Location: Houston, TX
|
Posted: Mon Dec 28, 2009 6:42 pm Post subject: Re: [AHK_L] BinObject - store binary data in objects |
|
|
| Lexikos wrote: | | I will probably re-enable this functionality in the next release. Note that it would then be possible to rewrite BinObject() to support the same syntax as the first example above, with better performance and less (script) code. |
Thanks. I can't wait.
I never really appreciated object oriented programming. I always thought it caused more confusion and constraints than was worth. But the way you have implemented it (without loss of flexibility), and demonstrated its uses with unparalleled examples in oop, I may be becoming a believer.  |
|
| Back to top |
|
 |
tinku99
Joined: 03 Aug 2007 Posts: 513 Location: Houston, TX
|
Posted: Wed Jan 06, 2010 2:26 pm Post subject: example use: picture object |
|
|
To practice with objects, I was reimplementing the bitmap_storage task on rosettacode for autohotkey. I have a question about meta __set function.
When I try to return a value as recommended in the rgb color example, my script doesn't work.
Is my script creating unnecessary key-value pairs on every call to Picture_Set ?
Also, I can't set picture.width anymore...
| Code: |
; adapted from Lexikos' AutoHotkey_L documentation
; http://www.autohotkey.net/~Lexikos/AutoHotkey_L/docs/Objects.htm
; requres binobject http://www.autohotkey.com/forum/viewtopic.php?t=52729
testPicture:
BinObject()
blue := Color(0)
blue.b := 255
cyan := Color(0x00ffff)
; MsgBox % "blue: " blue.R "," blue.G "," blue.B " = " blue.RGB
; MsgBox % "cyan: " cyan.R "," cyan.G "," cyan.B " = " cyan.RGB
square := Picture(10, 10)
msgbox % "picture.width " square.width
picture.width := 12 ; doesn't work !!
msgbox % "picture.width " square.width
square.fill(0)
msgbox % "10,9: " . square[10, 9] . "`n5, 5: " . square[5, 5]
square.fill(blue.rgb)
square[5, 5] := cyan.rgb
msgbox % "10,9: " . square[10, 9] . "`n5, 5: " . square[5, 5]
return
Picture_Get(picture, x, y)
{
idx := ((x - 1) * picture.height + y)
return numget(picture.pixels.data, idx * 3
, "uint")
}
Picture_Set(picture, x, y, value)
{
idx := ((x - 1) * picture.height + y)
numput(value, picture.pixels.data, idx * 3, "uint")
; return idx ; this was causing problems
}
Picture_Fill(picture, color)
{
loop % picture.height
{
row := A_Index
loop % picture.width
{
column := A_Index
picture[column, row] := color
}
}
return picture
}
Color_Get(clr, name)
{
if name = R
return (clr.RGB >> 16) & 255
if name = G
return (clr.RGB >> 8) & 255
if name = B
return clr.RGB & 255
}
Color_Set(clr, name, val)
{
if name in R,G,B
{
val &= 255
if name = R
clr.rgb := (val << 16) | (clr.rgb & ~0xff0000)
else if name = G
clr.rgb := (val << 8) | (clr.rgb & ~0x00ff00)
else ; name = B
clr.rgb := val | (clr.rgb & ~0x0000ff)
; 'Return' must be used to indicate a new key-value pair should not be created.
; This also defines what will be stored in the 'x' in 'x := clr[name] := val':
return val
}
}
Picture(width = 0, height = 0
, depth = 1, frames = 3)
{
static PictureType
if !PictureType
PictureType := Object("__get" , "Picture_Get"
,"__set" , "Picture_Set"
,"fill", "Picture_Fill")
Bitmap := Object("width", width
,"height", height
,"depth", depth ; in bytes, per pixel
,"frames", frames ;
,"base" , PictureType )
Bitmap.pixels := BinObject()
size := width * height * depth * frames
Bitmap.pixels.binsize["data"] := size
Return Bitmap
}
Color(rgb)
{
static ColorType
if !ColorType
ColorType
:= Object("__get" , "Color_Get"
, "__set" , "Color_Set")
return Object("rgb" , rgb
, "base" , ColorType)
}
!r::reload
!q::exitapp
#include binobject.ahk |
|
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7293 Location: Australia
|
Posted: Mon Feb 08, 2010 5:59 am Post subject: Re: example use: picture object |
|
|
I apologise for not replying sooner; I hadn't been watching this topic. (I'll assume you haven't solved the problem yet, since you haven't posted to that effect.)
| tinku99 wrote: | | When I try to return a value as recommended in the rgb color example, my script doesn't work. | Could you be more specific? When I try it, the script crashes regardless of whether Picture_Set returns a value. It appears to be caused by poorly sized binary data (causing heap corruption); if I add the following debug code, the script triggers it twice:
| Code: | Picture_Set(picture, x, y, value)
{
idx := ((x - 1) * picture.height + y)
size := picture.pixels.binsize["data"]
if (idx * 3 >= size)
MsgBox bad index!
else
numput(value, picture.pixels.data, idx * 3, "uint")
; return idx ; this was causing problems
} | I'm not sure how your script is supposed to work, but I suggest you review how the size and offsets are calculated. Keep in mind that both are in bytes. It appears the default "depth" is one byte, however your code seems to assume colour values are 4 bytes ("uint"), yet offset calculation does not account for it (doesn't multiply by 4).
| Quote: | | Is my script creating unnecessary key-value pairs on every call to Picture_Set ? | Yes. If you don't return a value, [x,y] will store an object in [x] and a value in [x][y]. Subsequently, [x,z] probably won't trigger the meta-function you expect since [x] already exists. You should be returning a value.
If not returning a value allows your script to "work", it is probably because:
- allowing the objects to be created prevents some assignments from triggering the meta-functions, and therefore prevents NumPut from being called with a bad offset in some cases; and
- when NumPut is inevitably called with a bad offset, it might overwrite those objects instead of something more essential.
I think these issues accentuate the need for structured data wrappers (i.e. structs, C-like arrays, etc.).
| Quote: | | Also, I can't set picture.width anymore... | Could you ever?
| Code: | square := Picture(10, 10)
...
picture.width := 12 ; doesn't work !! |
The "Epilogue" code in my first post should work in the latest revision, L45. |
|
| Back to top |
|
 |
tinku99
Joined: 03 Aug 2007 Posts: 513 Location: Houston, TX
|
Posted: Tue Feb 09, 2010 12:25 am Post subject: base mechanism |
|
|
Thanks Lexikos.
My code confused myself. I rewrote it more simply: here
Could you explain the base mechanism, I am a little confused about it: | Code: | Color := Object("R", 0, "G", 0, "B", 255, "rgb", "Color_RGB")
blue := Object("B", 255, "base", Color)
MsgBox % "blue: " blue.R "," blue.G "," blue.B
MsgBox % blue.rgb() ; Displays 65535.
MsgBox % Color.rgb() ; Displays blank, why??
Color_RGB(clr) {
return clr.R << 16 | clr.G << 8 | clr.B
} |
| Lexikos wrote: | I think these issues accentuate the need for structured data wrappers (i.e. structs, C-like arrays, etc.).
The "Epilogue" code in my first post should work in the latest revision, L45. | Thanks for reenabling _Get/_SetCapacity on object.fields.
I am looking forward to support for structs, c-like arrays. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7293 Location: Australia
|
Posted: Tue Feb 09, 2010 7:54 am Post subject: Re: base mechanism |
|
|
| tinku99 wrote: | | Could you explain the base mechanism, | I already explained it in the documentation.
| Quote: | | MsgBox % blue.rgb() | The specific section of the documentation which explains this is:
| Quote: | When the key of an operation (the first or only parameter) has no pre-existing value, the object's base is invoked. When a standard base object is invoked, it does the following:
...
- If this is a get or call operation, search for a matching key in the base object's own fields.
...
Call: If found, the field is called with the target object as the first parameter. Source: AutoHotkey_L - Objects | When you call blue.rgb(), it fails to find blue.rgb and so uses blue.base.rgb instead. Only when a method is invoked through this mechanism is the target object (blue) automatically inserted into the parameter list.
Logic follows:
If blue directly contains rgb it has no particular meaning, so we assume blue itself isn't relevant to the function-call (and wouldn't be expected as a parameter to the function). On the other hand, since blue.base is intended to define the behaviour of blue, there's a good chance that blue is relevant, and may be expected and required by the function.
| Quote: | | MsgBox % Color.rgb() ; Displays blank, why?? | That's the same as f:="Color_RGB",%f%(); it won't work since it's missing a required parameter, but won't complain since it's essentially a dynamic function call.
Although in this particular example it isn't appropriate (since Color is intended to define the behaviour of colors, not define a particular color), it can be worked around like this:
| Code: | | Color := Object("R", 0, "G", 0, "B", 255, "base", Object("rgb", "Color_RGB")) |
If you need more information, please be more specific.
No, it doesn't...
Edit: In your Rosetta Code example, why use hotkeys? I would expect the sample to do its piece, then exit.
| Quote: | | img._SetCapacity(width * height) | This will allow img to hold up to width*height objects, but it will only ever hold at most height objects (see below).
| Quote: | | bitmap[height, width] := color | This will create an object for each unique 'height' value and create a field within said object for each unique 'width' value; i.e.
| Code: | bitmap[1, 1] := x
bitmap[1, 2] := y
bitmap[2, 1] := z
| This creates two objects in bitmap: bitmap[1] containing two fields (1=x and 2=y) and bitmap[2] containing one field (1=z).
The idea of representing colours with a single RGB value internally and using accessors for the individual components - rather than vice versa as in your example - is that it would be more common to retrieve all three components (i.e. rgb) than one or two individual components. Each object and invocation (each pixel object, clr.R, clr.G, etc.) has a cost. Bitmap_Fill is a particularly "good" example of where not to use objects. "Worse" example pending...
Reedit: Try this:
| Code: | ; Example at the bottom.
Bitmap(width = 1, height = 1, background = 0)
{
static BitmapType
if !BitmapType
BitmapType
:= Object("fill" , "Bitmap_Fill" ; img.fill(c)
, "__get" , "Bitmap_Get" ; img[...]
, "__set" , "Bitmap_Set") ; img[...] := ...
img := Object("width" , width
, "height", height
, "base" , BitmapType)
img._SetCapacity("_data", width * height * 4)
img.data := img._GetAddress("_data") ; Safe until _data is resized.
img.fill(background)
Return img
}
Bitmap_Get(bitmap, x, y, z="")
{
if z= ; return RGB values,
return v:=NumGet(bitmap.data, y*height + x*4)
else if (x="color") ; unless a color() is specifically requested
return color(v:=bitmap[y,z])
}
Bitmap_Set(bitmap, x, y, color, z="")
{
if (x="color") ; for symmetry:
x:=y, y:=color, color:=z ; params << 1
NumPut(IsObject(color)?color.RGB:color, bitmap.data, y*height + x*4)
return color
}
Bitmap_Fill(bitmap, color)
{
DllCall("ntdll\RtlFillMemoryUlong", "ptr", bitmap.data
, "uint", bitmap.width * bitmap.height * 4
, "uint", IsObject(color)?color.RGB:color)
}
Color(c_r, g="", b="")
{
static ColorType
if !ColorType
ColorType := Object("rgb", 0
, "__Set", "Color_Set"
, "__Get", "Color_Get")
if g= ; store an rgb value directly:
return Object("base", ColorType, "rgb", c_r)
else ; let Color_Set do all the calculations:
return Object("base", ColorType, "r", c_r, "g", g, "b", b)
}
Color_Get(clr, name)
{
static R=16,G=8,B=0
if name in R,G,B
return v := (clr.RGB >> %name%) & 255
}
Color_Set(clr, name, val)
{
static R=16,G=8,B=0
if name in R,G,B
{
val &= 255, n := %name%
v := clr.rgb := (val << n) | (clr.rgb & ~(0xff << n))
return val
}
}
test:
blue := color(0,0,255) ; rgb
cyan := color(0,255,255)
MsgBox % "blue:r,g,b,rgb " blue.R "," blue.G "," blue.B ", " blue.rgb
MsgBox % "cyan:r,g,b,rgb " cyan.R "," cyan.G "," cyan.B ", " cyan.RGB
blue_square := Bitmap(10, 10, blue)
x := color(blue_square[4,4]) ; get pixel(4,4)
msgbox % "blue: 4,4,R,G,B, RGB: " x.R ", " x.G ", " x.B ", " x.rgb
blue_square[4,4] := cyan ; set pixel(4,4)
x := blue_square.color[4,4] ; get pixel(4,4)
msgbox % "cyan: 4,4,R,G,B, RGB: " x.R ", " x.G ", " x.B ", " x.rgb
return
| Color(), Color_Get(), etc. are the same as the example under Meta-Functions, but more compact. Bitmap() shows how to use the new mode of _SetCapacity, and _GetAddress. Bitmap_Get and Bitmap_Set show how to index into a raw bitmap using NumGet/NumPut. They also demonstrate an indexable property^ "img.color[x,y]" which is the same as "img[x,y]" but returns a Color object instead of an RGB value. Bitmap_Fill shows the most efficient way I'm aware of for filling raw (32-bit) bitmap data (or any block of memory). |
|
| Back to top |
|
 |
tinku99
Joined: 03 Aug 2007 Posts: 513 Location: Houston, TX
|
Posted: Tue Feb 09, 2010 10:20 am Post subject: base mechanism |
|
|
| Quote: | | Bitmap_Fill is a particularly "good" example of where not to use objects. | I know. Your method is what I had tried to do initially, but I couldn't keep all the bit shifting and pointer arithmetic correct. So, I went back to a much less efficient (in space and time), but IMO more readable and writable way. But, thanks for demonstrating how to optimize.
Edit1: Also, it would be easier to adapt for grayscale images and other pixel types.
End Edit1
| Quote: | worked around like this:
| Code: |
Color := Object("R", 0, "G", 0, "B", 255, "base", Object("rgb", "Color_RGB")) |
| another workaround: | Code: | Color_RGB(clr) {
return clr.R << 16 | clr.G << 8 | clr.B
}
Color := Object("R", 0, "G", 0, "B", 255, "rgb", "Color_RGB")
blue := Object("B", 255, "base", Color)
MsgBox % blue.rgb() ; Displays 255
MsgBox % Color.rgb(Color) ; Displays 255 | But, I will use the base mechanism. The documentation is clear. Thanks for explaining the motivation.
I agree with your other points as well. |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|