[vJASS] ImageTools

Level 19
Joined
Mar 18, 2012
Messages
1,717
ImageTools

For you image needs


About Images in Warcraft III

There is an excellent documentation about the image handle on wc3.net.

Main issues when it come to images:

1. An invalid filepath crashes the game. 2. An invalid imagetype ( index ) crashes the game. 3. Using native DestroyImage on an invalid image handle crashes the game.
However:

1. Using coordinates out of bounds is safe. 2. Images can be rendered locally for a force or player.


library ImageTools
JASS:
// Written by BPower.
library ImageTools /*v2.0
*************************************************************************************
*
*   For your image needs.
*
*   Strictly speaking it provides two wrapper function for CreateImage & DestroyImage,
*   named function NewImage & function ReleaseImage.
*
*   You always want to use these wrapper functions! Why so? See required know-how.
* 
*************************************************************************************
*
*   Required know-how [ Image extends handles for Dummies ]:
*   -----------------
*    
*       1. An invalid filepath crashes the game.
*       2. An invalid image type crashes the game.
*       3. DestroyImage native on an invalid image handle crashes the game.
*
*       ImageTools prevents you from these fatal errors
*       plus prints out debug messages, so you can quickly fix your code.
*
*       Hint: A very nice image tutorial link - [url]http://www.wc3c.net/showthread.php?t=107737[/url]
*
*************************************************************************************
*     
*       To Deaod
*       -----------------------
*
*           For the original ImageUtils library ( [url]http://www.wc3c.net/showthread.php?t=107707[/url] )
*
*       To Bribe
*       -----------------------
*
*           For Table
*
*       To Vexorian
*       -----------------------
*
*           For ARGB
*
*************************************************************************************
*
*   */ uses /*
* 
*       */ Table         /* [url]http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/[/url]
*
************************************************************************************
*
*   1. Import instruction
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       Copy the ImageTools script and library Table into your map.

*   2. API
*   ¯¯¯¯¯¯
*       function NewImage takes string file, real sizeX, real sizeY, real posX, real posY, real posZ, integer imageType returns image
*           - Wrapper to the CreateImage native.
*           - Does not crash the game, if the created image handle is invalid.
*
*       function ReleaseImage takes image whichImage retuns nothing
*           - Wrapper to the DestroyImage native
*           - Does not crash the game if whichImage is invalid.
*           - Does not crash the game if whichImage is the first ever created image in the map.
*
*       function CreateImageCenter takes string file, real sizeX, real sizeY, real centerX, real centerY, real posZ, integer imageType returns image
*           - Wrapper to the NewImage. It creates the image centered at centerX, centerY equal to other handle objects ( i.e. units ).
*
*   3. Configuration
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       You don't have to setup anything.
*/

    globals
        /**
        *   The following are valid image types
        *   sorted from highest to lowest layer:
        */
        constant integer IMAGE_TYPE_SELECTION      = 1// above all other image types.
        constant integer IMAGE_TYPE_OCCLUSION_MASK = 3// above image type 2 and 4
        constant integer IMAGE_TYPE_INDICATOR      = 2// above image type 4
        constant integer IMAGE_TYPE_UBERSPLAT      = 4// lowest layer. Tinting affected by time of day
                                                      // and is drawn below fog of war.
           
        /**
        *   Those two are image types options in GUI's "Image - Create" wrapper of CreateImageBJ.
        *   Both are invalid image types and should not be used in any case.
        *   While an image with IMAGE_TYPE_SHADOW instantly crashes the game,
        *   an image using IMAGE_TYPE_TOPMOST will simply not be render-able.
        *   The game season however will continue normally.
        */
       
        constant integer IMAGE_TYPE_SHADOW         = 0// Will create an invalid image handle with an handle id of -1.
        constant integer IMAGE_TYPE_TOPMOST        = 5// Will create an invalid image handle, which can't be rendered.
               
   
        /**
        *   If an image handle is invalid, it gets the handle id -1.
        *   Once you are using this handle somewhere, Warcraft III crashes,
        *   hence ALWAYS create an image handle via function NewImage.
        */
        private constant integer INVALID_IMAGE_ID   = -1
       
        /**
        *   When using ARGB, this is the default color set on Image.create()
        */
    endglobals

    static if DEBUG_MODE then
        private function DebugMsg takes string s returns nothing
            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "|cffff0000IMAGE TOOLS ERROR:|r\n    " + "|cff99b4d1" + "[" + s + "]|r" )
        endfunction
    endif
   
    globals
        private Table table = 0
    endglobals
   
    function NewImage takes string file, real sizeX, real sizeY, real posX, real posY, real posZ, integer imageType returns image
        local image i = CreateImage(file, sizeX, sizeY, 0, posX, posY, posZ, 0, 0, 0, imageType)
        if (0 > GetHandleId(i)) then
            debug if (imageType < IMAGE_TYPE_SELECTION) or (imageType > IMAGE_TYPE_UBERSPLAT) then
                debug call DebugMsg("function NewImage: Invalid image type [" + I2S(imageType) + "] for:  " + file)
            debug else
                debug call DebugMsg("function NewImage: Can't find string path in data: " + file)
            debug endif
            return null
        else
            set table.boolean[GetHandleId(i)] = true
            return i
        endif
    endfunction
   
    function ReleaseImage takes image i returns nothing
        local integer id = GetHandleId(i)
        if (id > 0) and (table.boolean.has(id)) then
            call table.boolean.remove(id)
            call DestroyImage(i)
        debug elseif (id > 0) then
            debug call DebugMsg("function ReleaseImage: Attempt to double destroy an image handle: [" + I2S(id) + "]!" )
            debug call DebugMsg("function ReleaseImage: Or even worse, you created an image without using NewImage!" )
        debug else
            debug call DebugMsg("function ReleaseImage: Attempt to destroy an invalid image handle! ( null )")       
        endif
    endfunction
   
    function CreateImageCenter takes string file, real sizeX, real sizeY, real centerX, real centerY, real centerZ, integer imageType returns image
        return NewImage(file, sizeX, sizeY, centerX - sizeX*.5, centerY - sizeY*.5, centerZ, imageType)
    endfunction
   
    private module InitImageTools
        private static method onInit takes nothing returns nothing
            set table = Table.create()
        endmethod
    endmodule
    private struct I extends array
        implement InitImageTools
    endstruct
   
    //! runtextmacro optional IMAGE_TOOLS_IMPORT_STRUCT_CODE()
   
endlibrary

Optional: ImageStruct

ImageTools is a minimalist library without any fancy features. Which is a good thing.
ImageStruct is an add-on to ImageTools, which enables struct syntax for image handles. struct Image extends array
Basically it's a textmacro which runs optional below the ImageTools code, if you have library ImageStruct in you map.


JASS:
library ImageStruct/*v1.0
*************************************************************************************
*
*   ImageStruct enables struct syntax with images.
*   Useful when you have advanced systems which relies on images.
* 
************************************************************************************
*
*   */ uses /*
* 
*       */ ImageTools    /* [url]http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/[/url]
*       */ optional ARGB /* [url]http://www.wc3c.net/showthread.php?t=101858[/url]
*
************************************************************************************
*
*       struct Image extends array
* 
*           Creator/Destructor:
*               static method create takes string path, real sX, real sY, real pX, real pY, real pZ, integer imageTypeIndex, boolean render returns thistype
*                   - Allocates a new Image instance and calls NewImage.
*                   - Automatically renders the image if "render" is true.
*
*               method destroy takes nothing returns nothing
*
*           Methods which automatically re-draw the image:
*
*               method setSizeX takes real size returns nothing
*               method setSizeY takes real size returns nothing
*               method setFile  takes string filepath returns nothing
*               method setImageType takes integer index returns nothing
*              
*           Methods which automatically re-locate the image:
*      
*               method setPosX takes real x returns nothing
*               method setPosY takes real y returns nothing
*               method setPosZ takes real z returns nothing
*               method setPosition takes real x, real y, real z returns nothing
*               method wrap takes boolean flag returns nothing
*                   - binds the image to the ground no matter what posZ is set.
*
*           Additional render options, automatically re-renders the image.
*
*               method renderForAll takes nothing returns nothing
*                   - renders this image for all players.
*
*               method renderForForce takes force whichForce returns nothing
*                   - renders this image for all players in force whichForce.
*
*               method renderLocalPlayer takes player whichPlayer returns nothing
*                   - renders this image only for a local client ( whichPlayer ).
*
*           Colorize images with ARGB:
*               method setColor takes ARGB paint returns nothing
*
*           Colorize images without ARGB:
*               method setColor takes integer Red, integer Green, integer Blue, integer Alpha returns nothing
*
*           Extra:
*
*               method setPositionEx takes real x, real y, real z returns nothing
*                   - Here x, y, z define the center of the image, similar to
*                     the coordinates which you use in other handle type
*                     i.e. CreateUnit(p, id, x, y , 0)
*
*           Readonly fields:
*
*               readonly image   img
*               readonly string  file
*               readonly real    sizeX
*               readonly real    sizeY
*               readonly real    posX
*               readonly real    posY
*               readonly real    posZ
*               readonly boolean show
*               readonly integer imageType
*               readonly boolean wrapped
*               readonly integer green
*               readonly integer red
*               readonly integer blue
*               readonly integer alpha
*
*   3. Configuration
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       If you are using library ARGB, you should define a default color.
*  
*/
    globals
        constant integer DEFAULT_IMAGE_COLOR = 0xFFFFFFFF
    endglobals
   
//! textmacro IMAGE_TOOLS_IMPORT_STRUCT_CODE

    private keyword IMAGE_TOOLS_M
    struct Image extends array   
        implement IMAGE_TOOLS_M
   
        readonly image   img
        readonly string  file
        readonly real    sizeX
        readonly real    sizeY
        readonly real    posX
        readonly real    posY
        readonly real    posZ
        readonly boolean show
        readonly integer imageType
        readonly boolean wrapped
        private  force   frc
        private  player  user

        private method paint takes nothing returns nothing
            static if LIBRARY_ARGB then
                call SetImageColor(img, red, green, blue, alpha)
            else
                if hasColor then
                    call SetImageColor(img, red, green, blue, alpha)
                endif
            endif
        endmethod
       
        static if LIBRARY_ARGB then
            readonly ARGB tint
       
            method operator red takes nothing returns integer
                return tint.red
            endmethod
            method operator green takes nothing returns integer
                return tint.green
            endmethod
            method operator blue takes nothing returns integer
                return tint.blue
            endmethod
            method operator alpha takes nothing returns integer
                return tint.alpha
            endmethod
            method operator color takes nothing returns ARGB
                return tint
            endmethod
            method setColor takes ARGB cool returns nothing
                set tint = cool
                call paint()
            endmethod
        else
            readonly integer green
            readonly integer red
            readonly integer blue
            readonly integer alpha
            private boolean hasColor
            method setColor takes integer Red, integer Green, integer Blue, integer Alpha returns nothing
                set alpha    = A/*  */lpha
                set red      = R/*  */ed
                set green    = G/*  */reen
                set blue     = B/*  */lue
                set hasColor = true
                call paint()
            endmethod
        endif
       
        private method draw takes nothing returns nothing
            if (img != null) then
                call ReleaseImage(img)
                set img = null
            endif
            set img = NewImage(file, sizeX, sizeY, posX, posY, posZ, imageType)
            call SetImageConstantHeight(img, not wrapped, posZ)
            call paint()
            if (user == null) and (frc == null) then
                call SetImageRenderAlways(img, show)
            elseif (user != null) then
                call SetImageRenderAlways(img, (show) and (GetLocalPlayer() == user))
            else
                call SetImageRenderAlways(img, (show) and IsPlayerInForce(GetLocalPlayer(), frc))
            endif
        endmethod
       
        method wrap takes boolean flag returns nothing
            set  wrapped = flag
            call SetImageConstantHeight(img, not wrapped, posZ)
        endmethod
       
        method renderForAll takes nothing returns nothing
            set user = null
            set frc  = null
            call draw()
        endmethod
       
        method renderLocalPlayer takes player whichPlayer returns nothing
            set user = whichPlayer
            call draw()
        endmethod
       
        method renderForForce takes force whichForce returns nothing
            set frc = whichForce
            call draw()
        endmethod

        method setSizeX takes real value returns nothing
            set sizeX = value
            call draw()
        endmethod
       
        method setSizeY takes real value returns nothing
            set sizeY = value
            call draw()
        endmethod
       
        method setFile takes string filepath returns nothing
            set file = filepath
            call draw()
        endmethod
       
        method setPosX takes real amount returns nothing
            set posX = amount
            call SetImagePosition(img, amount, posY, posZ)
        endmethod
       
        method setPosY takes real amount returns nothing
            set posY = amount
            call SetImagePosition(img, posX, amount, posZ)
        endmethod
       
        method setPosZ takes real value returns nothing
            set posZ = value
            call SetImageConstantHeight(img, true, value)
        endmethod
       
        method setPositionEx takes real x, real y, real z returns nothing
            set posX = x - sizeX*.5
            set posY = y - sizeY*.5
            set posZ = z
            call SetImagePosition(img, posX, posY, posZ)
            call SetImageConstantHeight(img, not wrapped, z)
        endmethod
       
        method setPosition takes real x, real y, real z returns nothing
            set posX = x
            set posY = y
            set posZ = z
            call SetImagePosition(img, posX, posY, posZ)
            call SetImageConstantHeight(img, not wrapped, z)
        endmethod
       
        // Checks for errors in NewImage
        method setImageType takes integer i returns nothing
            set imageType = i
            call SetImageType(img, i)
            call draw()
        endmethod
       
        static method create takes string path, real sX, real sY, real pX, real pY, real pZ, integer imageTypeIndex, boolean render returns thistype
      local thistype this = thistype.allocate()
            set file      = path
            set show      = render
            set sizeX     = sX
            set sizeY     = sY
            set posX      = pX
            set posY      = pY
            set posZ      = pZ
            set wrapped   = false
            set imageType = imageTypeIndex
            static if not LIBRARY_ARGB then
                set hasColor = false
            else
                set tint  = DEFAULT_IMAGE_COLOR
            endif
            call draw()
            return this
        endmethod

        method destroy takes nothing returns nothing
            if (img != null) then
                call ReleaseImage(img)
                set img = null
            endif
            set user = null
            set frc  = null
        endmethod
       
    endstruct   
   
    private module IMAGE_TOOLS_M
        private static integer array recycler
        static method allocate takes nothing returns thistype
            local thistype this = recycler[0]
            debug if (this == 8192) then
                debug call DebugMsg("Overflow - Requires an allocation module above 8910!")
                debug return 0
            debug endif
            if (0 == recycler[this]) then
                set recycler[0] = this + 1
            else
                set recycler[0] = recycler[this]
            endif
            debug set recycler[this] = -1
            return this
        endmethod
   
        method deallocate takes nothing returns nothing
            debug if (recycler[this] != -1) then
                debug call DebugMsg("Attempted To Deallocate Null Instance. ( Double Free )")
            debug endif
            set recycler[this] = recycler[0]
            set recycler[0] = this
        endmethod
       
        private static method onInit takes nothing returns nothing
            set recycler[0] = 1
        endmethod
    endmodule
//! endtextmacro

endlibrary

An image utility library exists already! Why a new one?

Yes true, in early 2010 Deaod submitted his version of ImageUtils on wc3.net
But I saw a few things, which I think can be done better.
  • imagex is a terrible struct name.
  • -
  • Images allocated in struct imagex do not allow specific render options ( for a single player or a force ), they render always for all players.
  • -
  • His double free protection is terrible as image handle id's can exceed 8910 easily. Consider that each unit uses at least 1 image, his shadow.
  • -
  • There is no connection between his imagex struct and the MAX_IMAGES constant. Yet it is used to define the struct space index.
  • -
  • The debug msg doesn't give enough precise information about the error. Just that an error occured.
  • -
  • Requires ARGB. Not that ARGB is bad, but why is it mandatory?
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,717
There is one thing which was not documented in the Images in Jass thread on wc3.net:

If you create an image before any image ( i.e. unit shadow ) has been created internally by Warcraft III
that image will recieve the handle id 0. You cannot destroy that image, not even with ReleaseImage.
It would crash the game according to my tests. That's why ReleaseImage only allows id which are > 0.

ImageUtils compiles in debug/non debug, with and without ARGB
I didn't test it thorough, but I will do that now. I only tested the small demo code above.

Question:
There is a specific trick to access the shadow of a unit. It abuses the way, handle ids are given to image handles.
Should I add that function, which allows you to create a unit with a custom/no shadow?

I don't have full backwards-compatibilty to the old ImageUtils, which would be required for TextSpalt ( also from Deaod).
What do you think. Should I use the same API as he did?
 
Cool stuff.
- images aren't reference counted
- i think accessing the shadow of a unit or creating a unit with a custom/no shadow could be in a separate snippet
- if you don't have full backwards compatibility I'd recommend renaming the library. Having more than one library under the same name can lead to a lot of ambiguity and weird situations if they don't have the same API. Sometimes it can also be a bit limiting to your system/creative design if you have to mirror everything with an existing system. :)

Also, maybe add an additional note on how renderForPlayer and renderForForce works. I could see someone accidentally trying to use renderForPlayer to display the same image for two players. It should be noted that renderForPlayer only renders for one player at a time (hides it for other players).
 
Level 19
Joined
Mar 18, 2012
Messages
1,717
I choose a new name for the library: ImageTools.

- images aren't reference counted
Good to know. I optimized function NewImage. It now returns the local
image handle directly and does no long set it to bj_lastCreatedImage.

I re-named renderForPlayer to renderLocalPlayer.

JASS:
           Additional render options, automatically re-renders the image.
*               method renderForAll      takes nothing returns nothing
*                   - renders this image for all players.
*               method renderForForce    takes force whichForce returns nothing
*                   - renders this image for all players in force whichForce.
*               method renderLocalPlayer takes player whichPlayer returns nothing
*                   - renders this image only for a local client ( whichPlayer ).
I was honestly wondering, if the struct wrapper around images is useful or not.
Instead I could remove it completly and just leave functions NewImage and ReleaseImage,
as they are the two functions you always want to use with image handles.

I guess you could use the Image struct in a more advanced library ( i.e. something like TextSplat. )
But in general I don't see so much use of Image.create over NewImage. It's just fancy syntax sugar.
 
Level 19
Joined
Mar 18, 2012
Messages
1,717
I split the code into two libraries:

1. ImageTools, which only provides function NewImage and function ReleaseImage.
I also added function CreateImageCenter, which is a wrapper to NewImage.
It will center the image to the passed coordinates.
So the visual outcome of CreateUnit(p, 'hfoo', 0, 0, 0) equals CreateImageCenter(s, 100, 100, 0, 0, 0, 3)

2.ImageStruct, which is only a textmacro.
//! runtextmacro optional IMAGE_TOOLS_IMPORT_STRUCT_CODE() is located at the bottom of ImageTools and enables optionally struct syntax for images.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
JASS:
        /**
        *   When using ARGB, this is the default color set on Image.create()
        */
endglobals

That aint much configuration there :D

I have read the other comments, but I have to disagree, I dont know what benefits it has to split this into two different systems, when its basically 3 functions in one, and entire wrapper in another one, I think they could've done just as easily in one file.

JASS:
                set img = null
            endif
            set img = NewImage(file, sizeX, sizeY, posX, posY, posZ, imageType)

the set img = null is useless, you overwrite it afterwards immediately with no other conditional code path.

Wrap has weird name imo, because you dont actually wrap the image around anything, you more like bind it to the ground's height, but this is nothing detremential.

Its good resource, if for nothing else just because it guards the images from crashing the game. I do want some more input on the opinion ot this being split to two different files tho.
 
It's approveable, seems good. Would you want to link to a demo code or so, if you have one?

Edit:

Resource Comment:

The splitting is fine for me. Seems reasoned.
Good to have something here on hive.
The only little mistake I mentioned is the debug message is written "IMAGE UTILS" instead of "IMAGE TOOLS".
I will change it and it's okay.

Approved.
 
Last edited:
Level 26
Joined
Jul 18, 2010
Messages
1,814
One should use originValues. They beeing 0 is the reason for images only be displayed when the bottom left corner is on the screen and one is needing to add offsets for everything when using Images.
Draw it directly at the given x/y/z and use origin xSize/2, ySize/2. When using that originValues the image will be drawn where one wants and Ingame it will be displayed, if the center of the image is almost on the screen, which matters a lot for big Images.

This originValues can make the given x/y/z be the center of the image, instead of beeing the bottom left corner.

Example: Draw an Image at Units Position
JASS:
function CreateUnitImage takes string file, unit u, real size returns nothing
   set bj_lastCreatedImage = CreateImage(file, size, size, 0, GetUnitX(u), GetUnitY(u), 0, size/2, size/2, 0, 2)
endfunction
 
Top