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 

[AHK_L] BinObject - store binary data in objects

 
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
Lexikos



Joined: 17 Oct 2006
Posts: 7293
Location: Australia

PostPosted: Mon Dec 28, 2009 11:07 am    Post subject: [AHK_L] BinObject - store binary data in objects Reply with quote

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
View user's profile Send private message Visit poster's website
majkinetor



Joined: 24 May 2006
Posts: 4511
Location: Belgrade

PostPosted: Mon Dec 28, 2009 11:56 am    Post subject: Reply with quote

Great. Thank you.
_________________
Back to top
View user's profile Send private message
tinku99



Joined: 03 Aug 2007
Posts: 513
Location: Houston, TX

PostPosted: Mon Dec 28, 2009 6:42 pm    Post subject: Re: [AHK_L] BinObject - store binary data in objects Reply with quote

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. Shocked
Back to top
View user's profile Send private message Send e-mail Visit poster's website
tinku99



Joined: 03 Aug 2007
Posts: 513
Location: Houston, TX

PostPosted: Wed Jan 06, 2010 2:26 pm    Post subject: example use: picture object Reply with quote

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
View user's profile Send private message Send e-mail Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 7293
Location: Australia

PostPosted: Mon Feb 08, 2010 5:59 am    Post subject: Re: example use: picture object Reply with quote

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? Wink
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
View user's profile Send private message Visit poster's website
tinku99



Joined: 03 Aug 2007
Posts: 513
Location: Houston, TX

PostPosted: Tue Feb 09, 2010 12:25 am    Post subject: base mechanism Reply with quote

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
View user's profile Send private message Send e-mail Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 7293
Location: Australia

PostPosted: Tue Feb 09, 2010 7:54 am    Post subject: Re: base mechanism Reply with quote

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.
Quote:
; Displays 65535.
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
View user's profile Send private message Visit poster's website
tinku99



Joined: 03 Aug 2007
Posts: 513
Location: Houston, TX

PostPosted: Tue Feb 09, 2010 10:20 am    Post subject: base mechanism Reply with quote

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
View user's profile Send private message Send e-mail Visit poster's website
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Page 1 of 1

 
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