• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] RGBA

JASS:
library RGBA
/*
Code by Guhun aka. SonGuhun
MIT license
Version 1.0.0

=========
 Description
=========

    This library defines the immutable struct RGBA, which can be used to store color data. These objects are internally
just integers, in which each byte pair represents a channel. This means you don't have to worry about destroying them to
free up memory.
  
=========
 Documentation
=========
  
    struct RGBA:
  
        Constants:
            integer R  -> Represents the red channel and can be used in methods that require a channel.
            integer G  -> Represents the green channel and can be used in methods that require a channel.
            integer B  -> Represents the blue channel and can be used in methods that require a channel.
            integer A  -> Represents the alpha channel and can be used in methods that require a channel.
          
            integer RGB  -> Represents all except the alpha channel and can be used in methods that require a channel.
  
        Fields:
            integer red
            integer green
            integer blue
            integer alpha
          
        Methods:
      
            . These methods return a new RGBA object, with an altered value for a channel:
            .   RGBA withRed(integer value)
            .   RGBA withBlue(integer value)
            .   RGBA withGreen(integer value)
            .   RGBA withAlpha(integer value)
          
            RGBA clearChannels(integer channels)
            .
            . Returns a new RGBA object, with zero as the value for all specified channels. To specify more than one channel, use their sum.
          
            RGBA setChannel(integer channel, integer value)
            .
            . Returns a new RGBA object with the specified channel altered. Cannot handle more than a single channel at once.
            . This is the only method of the RGBA struct which is not inlined by JassHelper.
          
            Static:
                RGBA create(integer r, integer g, integer b, integer a)
                RGBA alphaFirst(integer a, integer g, integer b, integer a)  -> Wrapper over BlzConvertColor. Should be marginally faster than create().
                RGBA newRGB(integer r, integer g, integer b)  -> Uses 255 as the value for the alpha channel.
              
                RGBA newRed(integer value)    -> Creates a new RGBA object with only a red channel.
                RGBA newGreen(integer value)  -> Creates a new RGBA object with only a green channel.
                RGBA newBlue(integer value)   -> Creates a new RGBA object with only a blue channel.
                RGBA newAlpha(integer value)  -> Creates a new RGBA object with only a alpha channel.
*/
//==================================================================================================
//                                       Usage Examples
//==================================================================================================
//! novjass

// Channel values must be in the range of 0 to 255. There is not protection against overflow.
RGBA yellow = RGBA.newRGB(255, 255, 0)
RGBA yellow_transparent = RGBA.create(255, 255, 0, 127)

RGBA magenta = yellow.clearChannels(RGBA.G).setChannel(RGBA.B, 255)
RGBA cyan = magenta.withRed(0).withGreen(255)

// We can add two RGBA colors, but first we must make sure that they don't share any channels to avoid overflow.
// Since we are clearing the alpha channel, we can add a new color which only has an alpha channel.
RGBA red = yellow_transparent.clearChannels(RGBA.G + RGBA.A) + RGBA.newAlpha(255)

RGBA transparent = yellow_transparent.clearChannels(RGBA.RGB)

RGBA black = RGBA.newAlpha(255) or 0xff000000
RGBA white = RGBA.newRGB(255, 255, 255) or 0xffffffff


// INVALID USAGE
RGBA invalid = magenta.setChannel(RGBA.RGB, 127)  // Cannot use RGB in setChannel, because it contains more than 1 channel.

//! endnovjass
//==================================================================================================
//                                        Source Code
//==================================================================================================

// Masks used to obtain values for each channel.
private struct Masks extends array

    static method operator RED takes nothing returns integer
        return $00FF0000
    endmethod
  
    static method operator GREEN takes nothing returns integer
        return $0000FF00
    endmethod
  
    static method operator BLUE takes nothing returns integer
        return $000000FF
    endmethod
  
    static method operator ALPHA takes nothing returns integer
        return $FF000000
    endmethod
  
    static method operator GBA takes nothing returns integer
        return $FF00FFFF
    endmethod
  
    static method operator RBA takes nothing returns integer
        return $FFFF00FF
    endmethod
  
    static method operator RGA takes nothing returns integer
        return $FFFFFF00
    endmethod
  
    static method operator RGB takes nothing returns integer
        return $00FFFFFF
    endmethod
  
endstruct

// When setting the value for a channel, the value must first be multiplied by one of the constants below.
private struct Multipliers extends array
    static method operator ALPHA takes nothing returns integer
        return $1000000
    endmethod
  
    static method operator RED takes nothing returns integer
        return $10000
    endmethod
  
    static method operator GREEN takes nothing returns integer
        return $100
    endmethod
endstruct

struct RGBA extends array
  
    static method operator R takes nothing returns integer
        return Masks.RED
    endmethod
  
    static method operator G takes nothing returns integer
        return Masks.GREEN
    endmethod
  
    static method operator B takes nothing returns integer
        return Masks.BLUE
    endmethod
  
    static method operator A takes nothing returns integer
        return Masks.ALPHA
    endmethod
  
    static method operator RGB takes nothing returns integer
        return Masks.RGB
    endmethod


    method operator red takes nothing returns integer
        return BlzBitAnd(this, Masks.RED) / Multipliers.RED
    endmethod
  
    method operator green takes nothing returns integer
        return BlzBitAnd(this, Masks.GREEN) / Multipliers.GREEN
    endmethod
  
    method operator blue takes nothing returns integer
        return BlzBitAnd(this, Masks.BLUE)
    endmethod
  
    // This is a special case, since all integers are signed in JASS.
    method operator alpha takes nothing returns integer
        return ModuloInteger(BlzBitAnd(this, Masks.ALPHA) / Multipliers.ALPHA, 256)
        /*
        Below is a faster implementation, but it's not inlined by JassHelper
      
        if this >= 0 then
            return this / Multipliers.ALPHA
        else
            return 256 + this/Multipliers.ALPHA
        endif
        */
    endmethod
  
  
    method withRed takes integer val returns thistype
        return BlzBitAnd(this, Masks.GBA) + Multipliers.RED * val
    endmethod
  
    method withGreen takes integer val returns thistype
        return BlzBitAnd(this, Masks.RBA) + Multipliers.GREEN * val
    endmethod
  
    method withBlue takes integer val returns thistype
        return BlzBitAnd(this, Masks.RGA) + val
    endmethod
  
    method withAlpha takes integer val returns thistype
        return BlzBitAnd(this, Masks.RGB) + Multipliers.ALPHA * val
    endmethod
  
  
    method clearChannels takes integer channels returns thistype
        return BlzBitAnd(this, BlzBitXor(channels, 0xffffffff))
    endmethod
  
    method withChannel takes integer channel, integer val returns thistype
        return this.clearChannels(channel) + (channel/255 * val)
    endmethod
  
  
    static method create takes integer r, integer g, integer b, integer a returns thistype
        return Multipliers.RED * r + Multipliers.GREEN * g + b + Multipliers.ALPHA * a
    endmethod
  
    static method alphaFirst takes integer a, integer r, integer g, integer b returns thistype
        return BlzConvertColor(a, r, g, b)
    endmethod
  
    static method newRGB takes integer r, integer g, integer b returns thistype
        return alphaFirst(0, r, g, b)
    endmethod
  
    static method newRed takes integer val returns thistype
        return Multipliers.RED * val
    endmethod
  
    static method newGreen takes integer val returns thistype
        return Multipliers.GREEN * val
    endmethod
  
    static method newBlue takes integer val returns thistype
        return val
    endmethod
  
    static method newAlpha takes integer val returns thistype
        return Multipliers.ALPHA * val
    endmethod
  
endstruct


endlibrary
 
@Bribe Sorry man, I had not seen your first message.
I think http://www.wc3c.net/showthread.php?t=101858 (accessible via archive.org) is still the golden standard.

There is also an approved vJass resource [vJASS] - Color.

Could you please clarify the benefit of this library over the others?

Afaict, the approved Color resource only handles RGB, not RGBA, so it can't do transparency and does not work with natives such as BlzFrameSetTextColor.

Regarding the original "ARGB" library by Vexorian, this library has the advantage of using the new natives, so it's a lot faster and generates almost no JASS code once you optimize the map after vJass inlines the functions.


I also chose to use a different approach in terms of setting channel values. By using methods instead of method operators, we can chain calls, like so:

ARGB:
JASS:
        set color.r = r
        set color.g = g
        set color.b = b
        set color.a = a

RGBA:
JASS:
        set color = color.withRed(r).withGreen(g).withBlue(b).withAlpha(a)

I also don't really agree with using method operators in this situation, because you can generate unexpected behavior:
JASS:
        local color = ARGB(255, 255, 255, 255)
        local color_reference = color

        set color.a = 0
        return color_reference
In the above code, color_reference is actually different from color, but for normal structs it would return the same object (or integer, in JASS).

Apart from that, this library is missing some additional features like mixing colors and such, which those libraries have. I guess it could be something nice to implement too, though I try to keep my code as modular and focused as possible. It would also be a good idea to just update ARGB to use the new natives, so people can just plug it into their existing codebases (and also use it without having to go through the Wayback Machine). But for the reasons outlined above I prefer the method style.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I think these are very good arguments for why this resource should be valid.

I would prefer the API to be more consistent and intuitive for the creation methods:

RGBA.create -keep as-is
RGBA.alphaFirst -> createARGB
RGBA.newRGB -> createRGB
RGBA newRed -> createRed
...same for blue and green

Alternatively, only use "new" and don't use the "create" name.

About the inlining function that performs better when it doesn't inline, I think we can both agree that one would prefer to have a simple function call that isn't inlined over two function calls and some math that are inlined in the compiled code.

I'm not sure on the naming for "withRed/Green" API. I think it makes more sense to say "fillRed" or "SetRed".

You could also do channel convention (similar to how I did table.boolean/real/unit in the Table library) and have API like:

myColor.channel.red(val).green(val)

The above would behave the same as myColor.withRed(val).withGreen(val).

If I wanted to use myColor.setChannel, what object do I need to pass as the parameter? Is it RGBA.R? Then how would I specify what I want RGBA.R set to - would I just pass RGBA.R + 127? If so, why use this function instead of withRed(127)?

The clearChannels method seems like it can handle more than one color to set to 0 at once, but I'm not sure what the API looks like to do that.
 
Top