Snippet String Colors

Nestharus

o-o
Reaction score
84
String Colors
By Nestharus and Sevion

Docs
  • What String Colors does:
    This colors strings with spiffy colors by converting RGB values to hexadecimal strings.

    This also supports gradients as well as segmented gradients.

    A segmented gradient is a gradient that is applied across the string multiple times. Each segment is a gradient. When a segment ends, the gradient restarts. If reversal is on, the gradient will reverse itself.

    For example, if the color is red and the gradient is green and the string is segmented 6 times

    Code:
    0      1        2      3        4      5        6
    red -> green -> red -> green -> red -> green -> red
  • How String Colors was made:
    Sevion started working on a library for coloring strings for a friend of his and I then got interested and started coding my own in parallel to his.

    I'd give him my code so that he could improve his own and at the very end we decided to submit the one I coded together since mine had the original code as well as segmented gradients.

    We went over the syntax and design together and added a method. I made initial docs, Sevion made drastic changes to those docs, and then I tweaked his changes.

    The post, ColorHex, mixing, and Segmented Gradients were not made in parallel, those were solely by me ; \.

  • Coloring a string:

    • [ljass]local string colored = green.apply("hello")[/ljass]
  • Coloring a string with RGB:

    • [ljass]local string colored = Color.convert(0, 255, 0)+"hello"+"|r"[/ljass]
  • Applying a gradient:

    • [ljass]local string colored = Color.Gradient.apply("hoho", red, green, 2, true)[/ljass]
  • Changing color RGB property:

    • [ljass]set myColor.green = 50[/ljass]
  • Working with raw colors:

    • [ljass]local ColorHex color = 0x00FF00[/ljass]
      [ljass]set color = color.setRed(0xFF) //0xFFFF00[/ljass]
      [ljass]set myString = color.apply(myString)[/ljass]
  • Mixing colors:

    • [ljass]set mixed = Color.mix(red, green, .5) //128, 128, 0[/ljass]
  • Coloring bars with ratios:

    • [ljass]call bar.asMix(min, max, ratio)[/ljass]

JASS:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ String Colors ~~ By Nestharus and Sevion ~~ Version 1.2.0.3 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//  What is String Colors?
//          -An easy way to define colors using RGB values and colorize your
//           game text.
//
//  API:
//          -struct Color extends array
//              -integer red
//                  Red value of the color.
//
//              -integer green
//                  Green value of the color.
//
//              -integer blue
//                  Blue value of the color.
//
//              -readonly string color
//                  Retreives the color code of a color struct.
//
//              -static method create takes integer red, integer green, integer blue returns thistype
//                  Creates a color for you to use.
//
//              -static method convert takes integer red, integer green, integer blue returns string
//                  Converts RGB into a hexidecimal color code.
//
//              -method apply takes string toBeColored returns string
//                  Colorizes the passed string with the struct's current color.
//
//              -static method mix takes Color color, Color gradient, real weight returns nothing
//                  Returns a mixed color by shifting color towards the gradient where 0 =< weight <= 1
//
//              -method asMix takes Color color, Color gradient, real weight returns nothing
//                  Sets this to a mix of color and gradient
//
//              -struct Gradient extends array
//                  -static method apply takes string toBeColored, Color color, Color gradient, integer segments, boolean reverses returns string
//                      Colorizes the passed string with the appropriate color gradient in
//                      the amount of segments defined.
//
//          -struct ColorHex extends array
//              -readonly integer red
//                  Retreives red value of the color.
//
//              -method setRed takes integer val returns integer
//                  Sets red value of the color.
//
//              -readonly integer green
//                  Retreives green value of the color.
//
//              -method setGreen takes integer val returns integer
//                  Sets green value of the color.
//
//              -readonly integer blue
//                  Retreives blue value of the color.
//
//              -method setBlue takes integer val returns integer
//                  Sets blue value of the color.
//
//              -readonly string color
//                  Retreives the color code of a color struct.
//
//              -static method create takes integer red, integer green, integer blue returns ColorHex
//                  Converts RGB into a hexidecimal integer.
//
//              -method apply takes string toBeColored returns string
//                  Colorizes the passed string with the struct's current color.
//
//              -static method mix takes ColorHex color, ColorHex gradient, real weight returns ColorHex
//                  Returns a mixed color by shifting color towards the gradient where 0 =< weight <= 1
//
//              -struct Gradient extends array
//                  -static method apply takes string toBeColored, integer color, integer gradient, integer segments, boolean reverses returns string
//                      Colorizes the passed string with the appropriate color gradient in
//                      the amount of segments defined.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

library StringColors
    public keyword ColorGradient
    public keyword HexColorGradient
    
    globals
        private string array hexChars
    endglobals
    
    struct ColorHex extends array
        public method operator red takes nothing returns integer
            return this/65536
        endmethod
        
        public method setRed takes integer val returns integer
            return this-this/256*256+(this/256-this/65536*256)*256+val*65536
        endmethod
        
        public method operator green takes nothing returns integer
            return this/256-this/65536*256
        endmethod
        
        public method setGreen takes integer val returns integer
            return this-this/256*256+val*256+this/65536*65536
        endmethod
        
        public method operator blue takes nothing returns integer
            return this-this/256*256
        endmethod
        
        public method setBlue takes integer val returns integer
            return this/256*256+val
        endmethod
        
        public method operator color takes nothing returns string
            return "|cff"+hexChars[this/65536]+hexChars[this/256-this/65536*256]+hexChars[this-this/256*256]
        endmethod
        
        public method apply takes string toBeColored returns string
            return color+toBeColored+"|r"
        endmethod
        
        public static method create takes integer red, integer green, integer blue returns thistype
            return (red*256+green)*256+blue
        endmethod
        
        public static method mix takes ColorHex color, ColorHex gradient, real weight returns ColorHex
            return (R2I(color/65536-(color/65536-gradient/65536)*weight+.5)*256+R2I((color/256-color/65536*256)-((color/256-color/65536*256)-(gradient/256-gradient/65536*256))*weight+.5))*256+R2I((color-color/256*256)-((color-color/256*256)-(gradient-gradient/256*256))*weight+.5)
        endmethod
        
        public static method operator Gradient takes nothing returns HexColorGradient
            return 0
        endmethod
    endstruct
    
    struct Color extends array
        private static integer instanceCount = 0
        private static integer array recycle
        private static integer recycleCount = 0
        
        private integer redX
        private integer greenX
        private integer blueX
        private string colorX
        
        public static method operator Gradient takes nothing returns ColorGradient
            return 0
        endmethod
        
        public static method convert takes integer red, integer green, integer blue returns string
            return "|cff" + hexChars[red]+hexChars[green]+hexChars[blue]
        endmethod
        
        public method apply takes string toBeColored returns string
            return colorX+toBeColored+"|r"
        endmethod
        
        public method operator red takes nothing returns integer
            return redX
        endmethod
        
        public method operator red= takes integer val returns nothing
            set redX = val
            set colorX = convert(redX, greenX, blueX)
        endmethod
        
        public method operator green takes nothing returns integer
            return greenX
        endmethod
        
        public method operator green= takes integer val returns nothing
            set greenX = val
            set colorX = convert(redX, greenX, blueX)
        endmethod
        
        public method operator blue takes nothing returns integer
            return blueX
        endmethod
        
        public method operator blue= takes integer val returns nothing
            set blueX = val
            set colorX = convert(redX, greenX, blueX)
        endmethod
        
        public method operator color takes nothing returns string
            return colorX
        endmethod
        
        public static method create takes integer red, integer green, integer blue returns thistype
            local thistype this
            
            if (recycleCount != 0) then
                set recycleCount = recycleCount - 1
                set this = recycle[recycleCount]
            else
                set instanceCount = instanceCount + 1
                set this = instanceCount
            endif
            
            set redX = red
            set greenX = green
            set blueX = blue
            set colorX = convert(red, green, blue)
            
            return this
        endmethod
        
        public method destroy takes nothing returns nothing
            set recycle[recycleCount] = this
            set recycleCount = recycleCount + 1
        endmethod
        
        private static method onInit takes nothing returns nothing
            local integer d0 = 16
            local integer d1
            set hexChars[0] = "0"
            set hexChars[1] = "1"
            set hexChars[2] = "2"
            set hexChars[3] = "3"
            set hexChars[4] = "4"
            set hexChars[5] = "5"
            set hexChars[6] = "6"
            set hexChars[7] = "7"
            set hexChars[8] = "8"
            set hexChars[9] = "9"
            set hexChars[10] = "A"
            set hexChars[11] = "B"
            set hexChars[12] = "C"
            set hexChars[13] = "D"
            set hexChars[14] = "E"
            set hexChars[15] = "F"
            
            loop
                set d0 = d0 - 1
                set d1 = 16
                loop
                    set d1 = d1 - 1
                    set hexChars[d0*16+d1] = hexChars[d0]+hexChars[d1]
                    exitwhen d1 == 0
                endloop
                exitwhen d0 == 0
            endloop
        endmethod
        
        public method asMix takes Color color, Color gradient, real weight returns nothing
            set this.red = R2I(color.red-(color.red-gradient.red)*weight+.5)
            set this.green = R2I(color.green-(color.green-gradient.green)*weight+.5)
            set this.blue = R2I(color.blue-(color.blue-gradient.blue)*weight+.5)
        endmethod
        
        public static method mix takes Color color, Color gradient, real weight returns thistype
            local thistype this
            
            if (recycleCount != 0) then
                set recycleCount = recycleCount - 1
                set this = recycle[recycleCount]
            else
                set instanceCount = instanceCount + 1
                set this = instanceCount
            endif
            
            set this.red = R2I(color.red-(color.red-gradient.red)*weight+.5)
            set this.green = R2I(color.green-(color.green-gradient.green)*weight+.5)
            set this.blue = R2I(color.blue-(color.blue-gradient.blue)*weight+.5)
            
            return this
        endmethod
    endstruct
    
    public struct HexColorGradient extends array
        public static method apply takes string toBeColored, integer color, integer gradient, integer segments, boolean reverses returns string
            local string colored //the colored string
            local integer length //length of string to color
            local integer position //current character position of string to color
            
            local real addRed //how much red to add
            local real addGreen //how much green to add
            local real addBlue //how much blue to add
            local real red //current red
            local real green //current green
            local real blue //current blue
            
            local integer colorRed
            local integer colorGreen
            local integer colorBlue
            
            local integer gradientRed
            local integer gradientGreen
            local integer gradientBlue
            
            local integer segment //current coloring segment
            local integer subPosition //sub position of segment
            local integer subLength //length of current segment (needed if string can't be split entirely)
            local real percent //percent of color to use
            local integer flag //neg flag
            
            //if the string exists, go on
            if (toBeColored != null and toBeColored != "") then
                set colorRed = color/65536
                set colorGreen = color/256-color/65536*256
                set colorBlue = color-color/256*256
                set gradientRed = color/65536
                set gradientGreen = color/256-color/65536*256
                set gradientBlue = color-color/256*256
                
                set length = StringLength(toBeColored)
                set colored = ""
                set flag = 0
                set red = colorRed
                set green = colorGreen
                set blue = colorBlue
                set position = 0
                //if result is a single character or micro segments
                //return the string as base color
                if (length == 1 or segments >= length or segments <= 0) then
                    set colored = "|cff"+hexChars[colorRed]+hexChars[colorGreen]+hexChars[colorBlue]+toBeColored
                elseif (segments > 1) then
                    //because subPosition starts at 0
                    //decrease by 1
                    set subLength = length/segments-1
                    //set sub position
                    set subPosition = 0
                    //all of the adders are just the complete as percents
                    //are used instead
                    set addRed = gradientRed - colorRed
                    set addGreen = gradientGreen - colorGreen
                    set addBlue = gradientBlue - colorBlue
                    //flag starts positive
                    set flag = 1
                    //segment starts at 0
                    set segment = 0
                    
                    loop
                        //add colored character to colored string
                        set colored = colored + Color.convert(R2I(red+.5), R2I(green+.5), R2I(blue+.5))+SubString(toBeColored, position, position+1)
                        set position = position + 1
                        exitwhen position == length
                        
                        //if hit end of sub, reverse direction of gradient
                        if (subPosition == subLength or (subPosition == 0 and flag < 1)) then
                            set segment = segment + 1
                            set subLength = (length-position)/(segments-segment)
                            if (reverses) then
                                set flag = flag * -1
                                if (subPosition != 0) then
                                    set subPosition = subLength
                                endif
                            else
                                set subPosition = 0
                            endif
                        endif
                        
                        //move ahead the sub sub string
                        set subPosition = subPosition + flag
                        //get current percentage of movement
                        set percent = I2R(subPosition)/subLength
                        //set colors by percent
                        set red = colorRed+addRed*percent
                        set green = colorGreen+addGreen*percent
                        set blue = colorBlue+addBlue*percent
                    endloop
                //process single segment gradient
                else
                    //because this starts on 0, set length to length-1
                    set subLength = length-1
                    
                    //adders are based on total length
                    set addRed = I2R(gradientRed - colorRed)/subLength
                    set addGreen = I2R(gradientGreen - colorGreen)/subLength
                    set addBlue = I2R(gradientBlue - colorBlue)/subLength
                    
                    loop
                        set colored = colored + Color.convert(R2I(red+.5), R2I(green+.5), R2I(blue+.5))+SubString(toBeColored, position, position+1)
                        
                        set position = position + 1
                        exitwhen position == length
                        
                        set red = red + addRed
                        set green = green + addGreen
                        set blue = blue + addBlue
                    endloop
                endif
                return colored+"|r"
            endif
            return null
        endmethod
    endstruct
    
    public struct ColorGradient extends array
        public static method apply takes string toBeColored, Color color, Color gradient, integer segments, boolean reverses returns string
            local string colored //the colored string
            local integer length //length of string to color
            local integer position //current character position of string to color
            
            local real addRed //how much red to add
            local real addGreen //how much green to add
            local real addBlue //how much blue to add
            local real red //current red
            local real green //current green
            local real blue //current blue
            
            local integer segment //current coloring segment
            local integer subPosition //sub position of segment
            local integer subLength //length of current segment (needed if string can't be split entirely)
            local real percent //percent of color to use
            local integer flag //neg flag
            
            //if the string exists, go on
            if (toBeColored != null and toBeColored != "") then
                set length = StringLength(toBeColored)
                set colored = ""
                set flag = 0
                set red = color.red
                set green = color.green
                set blue = color.blue
                set position = 0
                //if result is a single character or micro segments
                //return the string as base color
                if (length == 1 or segments >= length or segments <= 0) then
                    set colored = color.color+toBeColored
                elseif (segments > 1) then
                    //because subPosition starts at 0
                    //decrease by 1
                    set subLength = length/segments-1
                    //set sub position
                    set subPosition = 0
                    //all of the adders are just the complete as percents
                    //are used instead
                    set addRed = gradient.red - color.red
                    set addGreen = gradient.green - color.green
                    set addBlue = gradient.blue - color.blue
                    //flag starts positive
                    set flag = 1
                    //segment starts at 0
                    set segment = 0
                    
                    loop
                        //add colored character to colored string
                        set colored = colored + Color.convert(R2I(red+.5), R2I(green+.5), R2I(blue+.5))+SubString(toBeColored, position, position+1)
                        set position = position + 1
                        exitwhen position == length
                        
                        //if hit end of sub, reverse direction of gradient
                        if (subPosition == subLength or (subPosition == 0 and flag < 1)) then
                            set segment = segment + 1
                            set subLength = (length-position)/(segments-segment)
                            if (reverses) then
                                set flag = flag * -1
                                if (subPosition != 0) then
                                    set subPosition = subLength
                                endif
                            else
                                set subPosition = 0
                            endif
                        endif
                        
                        //move ahead the sub sub string
                        set subPosition = subPosition + flag
                        //get current percentage of movement
                        set percent = I2R(subPosition)/subLength
                        //set colors by percent
                        set red = color.red+addRed*percent
                        set green = color.green+addGreen*percent
                        set blue = color.blue+addBlue*percent
                    endloop
                //process single segment gradient
                else
                    //because this starts on 0, set length to length-1
                    set subLength = length-1
                    
                    //adders are based on total length
                    set addRed = I2R(gradient.red - color.red)/subLength
                    set addGreen = I2R(gradient.green - color.green)/subLength
                    set addBlue = I2R(gradient.blue - color.blue)/subLength
                    
                    loop
                        set colored = colored + Color.convert(R2I(red+.5), R2I(green+.5), R2I(blue+.5))+SubString(toBeColored, position, position+1)
                        
                        set position = position + 1
                        exitwhen position == length
                        
                        set red = red + addRed
                        set green = green + addGreen
                        set blue = blue + addBlue
                    endloop
                endif
                return colored+"|r"
            endif
            return null
        endmethod
    endstruct
endlibrary
 

Accname

2D-Graphics enthusiast
Reaction score
1,462
sounds quite good, could you upload some screenshots? i think they will be great for this sort of system.
 

Accname

2D-Graphics enthusiast
Reaction score
1,462
i just think it would look very nice if you show us how that text will look ingame. i know many people complaining that they cannot show screenshots for their submitted turtorials, systems, spells, whatsoever, i just said you can do it very nicely for this system.
 

Nestharus

o-o
Reaction score
84
i just think it would look very nice if you show us how that text will look ingame. i know many people complaining that they cannot show screenshots for their submitted turtorials, systems, spells, whatsoever, i just said you can do it very nicely for this system.

I think I'll pass : ).
 

Romek

Super Moderator
Reaction score
963
A demo map would be nice. :)
 

Nestharus

o-o
Reaction score
84
A demo map would be nice. :)

Since it's you Romek, I can get a demo map up asap = ). It won't be for around another 6 hours as I'm quite literally out of time atm ; (.


What would you like to see in the demo map? How about a classic hello world using gradients + mixing followed by a zooooooom : D.

edit
Nvm, I'll be using Cmd to process the direct commands in game = ).
 

Sevion

The DIY Ninja
Reaction score
413
[ljass]method asMix takes Color color, Color gradient, real weight returns nothing[/ljass] does not work as I initially thought...

If I pass Red and Green, it makes the bar completely yellow and as the ratio decreases, it darkens.

Edit: I'll work up a demo map...
 

Deaod

Member
Reaction score
6
Is this in any way superior to ARGB?
Vexorian even provided a "degrade" function as an example.

JASS:
function SegmentedGradient takes string message, ARGB from, ARGB to, integer segments, boolean reverse returns string
    local integer length=StringLength(message)
    local real charperseg=I2R(length)/segments
    local string result=""
    local integer i=0
    loop
        exitwhen i>=segments
        if i/2*2!=i and reverse then // i is uneven
            set result=result+Degrade(SubString(message, R2I(i*charperseg+.5), R2I((i+1)*charperseg+.5)), to, from)
        else
            set result=result+Degrade(SubString(message, R2I(i*charperseg+.5), R2I((i+1)*charperseg+.5)), from, to)
        endif
        set i=i+1
    endloop
    return result
endfunction

// Source: <a href="http://www.wc3c.net/showthread.php?t=101858" target="_blank" class="link link--external" rel="nofollow ugc noopener">http://www.wc3c.net/showthread.php?t=101858</a>
function Degrade takes string message, ARGB colorA, ARGB colorB returns string
 local integer i=0
 local integer L=StringLength(message)
 local string  r
    if(L==1) then
        return ARGB.mix(colorA,colorB,0.5).str(message)
    endif
    set r=&quot;&quot;
    loop
        exitwhen (i&gt;=L)
        set r=r+ARGB.mix(colorA,colorB, i*(1.0/ (L-1) ) ).str( SubString(message,i,i+1) )
        set i=i+1
    endloop
 return r
endfunction


there, that wasnt so hard.
 

Nestharus

o-o
Reaction score
84
Uh huh, and I'll keep saying this. Working with raw hexadecimal for everything is rather stupid = ). Furthermore, alpha channels on wc3 are broken for text, so including that in the math does nothing but bog everything down.

For most of your needs, using color objects is the best way to go. In very rare cases, integers might be useful, which is the only reason I included those = ).

I would personally prefer this over the raw hex of ARGB any day because of the 2 reasons I said above.

Oh yea, and mixing is officially working 100% now ; O. I don't know wth I was thinking with the math, lol.
 

Deaod

Member
Reaction score
6
I prefer ARGB(0xFFFFCC00) over ARGB.create(255, 255, 0xCC, 0) any time. Not to mention that passing a single integer is a few times more convenient (no need for cleanup, you can have color constants).

You can use colors for things other than text. Maybe alpha doesnt work on text, but it sure does on images.
 

Deaod

Member
Reaction score
6
because working with strings is messy. And using an integer is a bit more secure than letting the user input the correct data himself.
 

Sevion

The DIY Ninja
Reaction score
413
What?

What difference is there between

ARGB(0xFFFF0000) and |cFFFF0000

None except that ARGB is less efficient with text coloring (alpha which doesn't work on text).
 

Nestharus

o-o
Reaction score
84
Actually, there is a difference..

With the raw code-
ARGB(0xFFFF0000) and |cFFFF0000

This one is more efficient as it leaves out the alpha filter.

With the reference, this is more efficient as it just returns a string (inlined).

Whenever the fields change (r, g, b), it is 3 array reads (hexChars[r]+hexChars[g]+hexChars) to generate the string.


When looking at it from the convert, it just returns that string ("|cff+hexChars[r]+hexChars[g]+hexChars"), which again inlines.

I suppose alpha filter would be useful on images, but lucky for us this is made specifically for strings (which is why it doesn't have an alpha filter).

ARGB will never compare to this when it comes to strings. Use it for raw integers on images, it's perfect for that (even though I wouldn't use raw integers to begin with, lol... I'd do 0xFF, 0xFF, 0xFF, 0xFF over 0xFFFFFFFF). For temporary colors or w/e, I'd use a global color over raw hex ; P.


This can't actually even be used for images because it auto adds "|c" to the strings.

If you are doing string coloring, this is by far a better choice than ARGB. If you are doing image coloring, then ARGB is your only option atm.
 

Deaod

Member
Reaction score
6
JASS:
call IncreaseRed(0xFF000000)
function IncreaseRed takes ARGB color returns ARGB
    set color.red=color.red+16
    return color
endfunction
Note how this does NOT change the color passed to it.

Doing that with strings is a bit cumbersome and involves a lot of SubStrings and StringHash calls. With integers its just math which is a lot faster than function/native calls.

Fading units from one color to another is also something thats made very easy with ARGB (through its "mix" method).

"|cff+hexChars[r]+hexChars[g]+hexChars"
That wont inline (multiple references to parameter "this").

Also, id prefer having a single ARGB parameter over multiple integer parameters.

--

I think you should stop talking about strings in general. The |c tag has an alpha paramter, unfortunately its non-functional in all native cases i know. However, TextSplat supports that parameter (thats not a bug). Do me a favour and stop ignoring it.
 

Nestharus

o-o
Reaction score
84
Doing that with strings is a bit cumbersome and involves a lot of SubStrings and StringHash calls. With integers its just math which is a lot faster than function/native calls.

This doesn't do what you mentioned with strings... ... the only time strings are used is when the actually colored string is set, which is again 3 array reads + "|cff"

The |c tag has an alpha paramter, unfortunately its non-functional in all native cases i know. However, TextSplat supports that parameter (thats not a bug). Do me a favour and stop ignoring it.

guess this won't support Textsplats ; D.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top