Subscribe to this thread
Home - General / All posts - get style value
artlembo


3,440 post(s)
#17-Apr-25 01:28

In Manifold 8, I can obtain the color a an object in a drawing with

 r = obj.BackColor.Red

g = obj.BackColor.Green

b = obj.BackColor.Blue

then, I can combine them to get an RGB and hex value and put the result into a new field. Cool little trick. I can then pass that to Leaflet and now I have objects with colors - neat!

But, of course Manifold 8 can't export to .geojson. Manifold 9 can, but I don't think Manifold 9 can get the style color object of an object.

Does anyone know if there is a way to get an objects rendered color using Manifold 9?

apo
193 post(s)
#17-Apr-25 07:24

I also didn't found any information in the API about style and colors.

The main information, meaning at the layer level, is stored in the mfd_meta, under the "StyleAreaColorBack" and "StyleAreaColor" properties encoded in M9. This can be exploited to fill a new column.

You can retrieve the RGB values using one of the function given in this post, and find any script to code the RGB to Hex

https://georeference.org/forum/t159438.12#159439

This works fine using SQL at a layer level.

In the case you have a thematic coloring of your layer and want the exact color of each object using the theme, you have to distinguish between two situations

  • first fixed nominal or ordinal class colors, you can reinterprete the classes to attribute the good color, feasible reading the thresholds and the color values
  • second interpolated colors based on cardinal classes or values, you have to interpolate the colors, more complex
Dimitri


7,558 post(s)
#17-Apr-25 10:55

I also didn't found any information in the API about style and colors.

The API assumes you're familiar with the content of the user manual. Style and colors are discussed in the main documentation, for example, in the Example: Style Properties in the mfd_meta Table topic. The API provides programmatic means to do things with tables, such as reading values from tables.

As illustrated in the above example topic, it's easy to get style information that is used to color objects (I mean "object" as in points, lines and areas). All that is in the mfd_meta table, for example, the value for the StylePointColorBack property might be

"Field""mfd_id""Value": 15094100, "Values": { "1": 15094100, "10": 13940991, "2": 2537215, "3": 6809297, "4": 13465302, "5": 16763532, "6": 16773811, "7": 16747737, "8": 14261595, "9": 13169321 } }

The above says background color is determined by the mfd_id field. Take the mfd_id value for the object and the JSON string above tells you what color it is. For object with mfd_id 10 the color is 13940991.

Colors are given in standard 9 packed format. As apo has noted, there are threads in the forum about converting them to and from rgb and alpha. Works great using SQL or using scripting or programming languages, like C# or C++.

If you want to know the specific color applied to a given object, if there are unique values applied that's easy to determine from the JSON. If it's an interpolated color based on some value, it's straightforward to get the interpolated color based on that object's specific value and the two colors, call them p and q, that bound that value.

For example, using some C++ code fragments, suppose your colors are ColorBgra colors...

struct ColorBgra

{

 unsigned char _b; // blue component

 unsigned char _g; // green component

 unsigned char _r; // red component

 unsigned char _a; // alpha component

};

Blending (interpolating them) is conceptually simple:

// compose blend of p and q over a 0 to 1 range between p and q

Colors::ColorBgra Blend(const Colors::ColorBgra& p, const Colors::ColorBgra& q, double blend)

{

 if (blend <= 0)

 return p;

 if (blend >= 1)

 return q;

 return Colors::CreateColorBgra(BlendValue(p._b, q._b, blend), BlendValue(p._g, q._g, blend), BlendValue(p._r, q._r, blend), BlendValue(p._a, q._a, blend));

}

calling BlendValue to get the interpolated values for each channel...

unsigned char BlendValue(int p, int q, double blend)

{

 return (unsigned char)floor(p + blend * (q - p) + 0.5);

}

You just determine where your object's value lies in the range between colors p and q in the color set you're using to format color, express that as a value from 0 (all p) to 1 (all q), and blend using the above.

artlembo


3,440 post(s)
#17-Apr-25 11:20

Thanks! This is exactly what I was looking for. I will write back to this thread. Once I put together a solution in the form of a query or script

artlembo


3,440 post(s)
#18-Apr-25 01:57

getting closer. This query:

SELECT StringJsonValue([Value]'Values',FALSEAS c1

FROM [mfd_meta]

WHERE [Name] = 'PR Thematic Map 2'

AND [Property] = 'StyleAreaColorBack';

will return a json that looks like this:

{ "0": 56283, "17192": 2285535, "22715": 4515044, "27935": 6744553, "29275": 8974062, "30361": 11203571, "32015": 13432823, "32671": 15662332, "32709": 16576238, "33581": 16239820, "33786": 15968938, "35563": 15632520, "36520": 15295845, "39041": 14959427, "44612": 14623009, "52830": 14352384 }

now, I was hoping something like

StringToJsonArrayValues(<json>)

CALL StringToJsonArrayValues('{ "0": 56283, "17192": 2285535, "22715": 4515044, "27935": 6744553, "29275": 8974062, "30361": 11203571, "32015": 13432823, "32671": 15662332, "32709": 16576238, "33581": 16239820, "33786": 15968938, "35563": 15632520, "36520": 15295845, "39041": 14959427, "44612": 14623009, "52830": 14352384 }')

would return a table for the different keys. But, it just returns an empty table. I was hoping to pass the results of the SQL query into the StringToJsonArrayValues. Once I do that, I can join it to the drawing's table and update a [color] field with the color values.

Dimitri


7,558 post(s)
#18-Apr-25 06:36

There are no arrays in the string you used as an argument to StringToJsonArrayValues. A JSON array is an ordered list of values separated by commas and surrounded by square [ ] brackets, for example,

[ 0, 1, 2, 3, 4, 5]

The function returns an empty table because there is no array in the argument.

artlembo


3,440 post(s)
#18-Apr-25 12:45

thanks. Just to keep people up-to-date on my progress, I wrote a bit of probably overly complex SQL to obtain the Manifold color value to associate with the drawing color:

SELECT  CAST([color] AS int64) AS color, 

 CAST(StringReplace([uid],'"',''AS int64) AS uid

FROM

(

SELECT  StringStart([Value],StringFind([value]',')) AS color ,

 StringSubStringLen([Value],

 StringFind([value]',')+1,

 StringLength([Value])-1) AS uid

FROM 

(SELECT SPLIT CALL StringToToKens(c1,':')

FROM 

 (

  SELECT StringJsonValue([Value]'Values',FALSEAS c1

 FROM [mfd_meta]

 WHERE [Name] = 'PR Thematic Map 2'

 AND [Property] = 'StyleAreaColorBack'

 )

)

)

that gives me 2 columns. The next thing I have to do is update a field in the drawing to reflect the color. Finally, I'll have to convert the Manifold color code to a RGB or HEX to eventually export the drawing to a .geojson.

Dimitri


7,558 post(s)
#19-Apr-25 13:11

A few tips...

Regarding the discussion in https://georeference.org/forum/t159438.12#159439 ...

You have to be careful parsing that discussion because some posts use transparency and others opacity. Sloots, for example, in a post uses "alpha" meaning transparency, a number from 0 to 255 where 0 is no transparency (fully opaque) and 255 is fully transparent. apo, in contrast, in a post uses "Alpha" to mean percent opacity with values from 0 to 100.

Manifold color code numbers are packed ARGB where the high bits are alpha (transparency, 0 to 255). That's convenient because if there is no alpha (numbers currently used in various palettes used in styles have no alpha... that may change with the next build) it's a simple decay into RGB. But when you're constructing partially transparent colors it's conceptually easier, I think, to use percent opacity. For example, you may want a red that's 70% opaque, which corresponds visually to what you'd get if you had a layer with that red color in it that has a 70% opacity setting in the Layers pane. Using percent opacity also gets around the endemic problem in graphics where some people use the word "alpha" to mean transparency and others to mean opacity.

Converting R, G, B and percentOpacity into a Manifold packed ARGB number is easy. I do that in C# because I think the arithmetic logic is simpler and clearer than doing it in SQL. It's easy to call the C# function from SQL, too:

// C#

// A script named [RGBO to packed ARGB]

class Script

{

    public static uint PackColorARGB(uint red, uint green, uint blue, uint percentOpacity)

    {

        uint alpha = 255 - ((percentOpacity * 255 + 50) / 100);

        return (alpha << 24) | (red << 16) | (green << 8) | blue;

    }

    static void Main() { }

}

If you want to build a Manifold color from alpha instead of percent opacity just use alpha as an argument and eliminate the conversion from percentOpacity:

// C#

class Script

{

    public static uint PackColorARGB(uint red, uint green, uint blue, uint alpha)

    {

        return (alpha << 24) | (red << 16) | (green << 8) | blue;

    }

    static void Main() { }

}

The above are written using uint, so they don't bother clamping ranges for percent opacity to 0 to 100 and channels to 0 to 255.

It's simple to use C# scripts in your SQL. Save the script in your project using a convenient name, and then define a function using the script:

FUNCTION PackORGB(@r UINT32, @g UINT32, @b UINT32, @o UINT32) UINT32 AS SCRIPT [RGBO to packed ARGB] ENTRY 'Script.PackColorARGB';

And then use the function.

PackORGB([Red], [Green], [Blue], [OpacityPct]) 

The above could be an expression to create a new computed field that is a Manifold color code with transparency that is computed from four fields that give R, G, B and percent opacity values. The function definition goes in the Expression Context part of the expression builder when creating a computed field.

Suppose you already have a packed, RGB, Manifold color code and you want to get the corresponding packed ARGB Manifold color code for some percent opacity you want? That's very easy to do using function defined in a script.

Create a C# script called MCO to packed ARGB:

// C#

// A script named [MCO to packed ARGB]

class Script

{

    public static uint PackColorARGB(uint MC, uint percentOpacity)

    {

        uint alpha = 255 - ((percentOpacity * 255 + 50) / 100);

        return (alpha << 24) | MC;

    }

    static void Main() { }

}

You can then use it in your SQL by defining a function (put that in the Expression Context when using it to create computed fields):

cUNCTION PackMCO(@m UINT32, @o UINT32) UINT32 AS SCRIPT [MCO to packed ARGB] ENTRY 'Script.PackColorARGB';

... and then using the function in your SQL:

PackMCO([MC], [OpacityPct]

The example assumes your existing RGB Manifold color code is in a field called [MC]. Note that using such a PackMCO function is a lot more straightforward than what can be complex arithmetic inside SQL.

All of the above functions, by the way are set up to round up or down to the nearest integer when converting percent opacity to alpha.

If you want to convert alpha to percent opacity, that's easy, too. Here it is using int, with the input value of alpha clamped to 0 to 255 range with an if ... else if test:

// C#

// AlphaToPercentOpacity.cs

class Script

{

    public static int AlphaToPercentOpacity(int alpha)

    {

        if (alpha < 0) alpha = 0;

        else if (alpha > 255) alpha = 255;

        return ((255 - alpha) * 100 + 127) / 255;

    }

    static void Main() { }

}

For anybody that wants to extract alpha, R, G, and B from a Manifold packed ARGB color code, I do that using four functions, that can be put into their own script components:

// C#

// UnpackChannelA.cs

class Script

{

    public static uint UnpackColorA(uint packed)

    {

        uint alpha = (packed >> 24) & 0xFF;

        return (alpha == 0) ? 0 : alpha;

    }

    static void Main() { }

}

// C#

// UnpackChannelR.cs

class Script

{

    public static uint UnpackColorR(uint packed)

    {

        return (packed >> 16) & 0xFF;

    }

    static void Main() { }

}

// C#

// UnpackChannelG.cs

class Script

{

    public static uint UnpackColorG(uint packed)

    {

        return (packed >> 8) & 0xFF;

    }

    static void Main() { }

}

// C#

// UnpackChannelB.cs

class Script

{

    public static uint UnpackColorB(uint packed)

    {

        return packed & 0xFF;

    }

    static void Main() { }

}

These are simple to use from SQL, for example,

FUNCTION UnpackA(@m UINT32) UINT32 AS SCRIPT [UnpackChannelA] ENTRY 'Script.UnpackColorA';

And using the function...

UnpackA([MC]) 

... to get the alpha channel used in a packed ARGB value in a field called [MC]. Note that the alpha function works correctly even if the manifold code in MC was or was not created with an alpha value.

Converting values to Hex is also very simple.

A simple SQL way to do it if you have an RGB (no alpha) manifold color code was given by adamw in a post, with an example of use in the command window:

--SQL9

FUNCTION HexByte(@c UINT32NVARCHAR AS

  StringFormatNumber(@c MOD 256, 'X02''')

END;

FUNCTION HexRGB(@c UINT32NVARCHAR AS

  '#' + HexByte(@c / 256 / 256) + HexByte(@c / 256) + HexByte(@c)

END;

? HexRGB(3329330)

-- #32CD32

That takes advantage of the very rich StringFormatNumber SQL function in 9. It's trivial to extend that to converting an ARGB value (left as an exercise for the reader...). You may not need to preface the result with a # if you just want Hex strings to feed some other application.

It is wise to build in support for ARGB (with alpha) manifold color codes since that is what is built into the machinery and what people are using now. A general solution as a C# script:

// C#

// Convert Hex string to uint

class Script

{

    public static uint HexStringToUInt(string hex)

    {

        uint result = 0;

        for (int i = 0; i < hex.Length; i++)

 

       {

            char c = hex[i];

            uint value = 0;

            if (c >= '0' && c <= '9')

                value = (uint)(c - '0');

            else if (c >= 'A' && c <= 'F')

                value = (uint)(c - 'A' + 10);

            else if (c >= 'a' && c <= 'f')

                value = (uint)(c - 'a' + 10);

            else

                throw new System.ArgumentException("Invalid character in hex string.");

            result = (result << 4) | value;

        }

        return result;

    }

    static void Main() { }

}

Hope that helps!

artlembo


3,440 post(s)
#19-Apr-25 18:06

thanks. But, I think I'll just stick with 8. I was able to write and entire script that does everything that I need in under 10 minutes:

code.

..

..

for each obj in drwOS

    r = obj.BackColor.Red

    g = obj.BackColor.Green

    b = obj.BackColor.Blue

 hexColor = "#" & Hex2(r) & Hex2(g) & Hex2(b)

 Set objrec = obj.Record

 objrec.Data("color") =  hexColor

next

End Sub

Function Hex2(n)

    h = Hex(n)

    If Len(h) = 1 Then

        Hex2 = "0" & h

    Else

        Hex2 = h

    End If

End Function

that's all there was to it. Done!

Then, of course, there's this little gem using an ActiveColumn:

code

Function Func

 r = Record.Object.BackColor.Red

    g = Record.Object.BackColor.Green

    b = Record.Object.BackColor.Blue

 Func = "#" & Hex2(r) & Hex2(g) & Hex2(b)

End Function

Function Hex2(n)

    h = Hex(n)

    If Len(h) = 1 Then

        Hex2 = "0" & h

    Else

        Hex2 = h

    End If

End Function

Manifold 8 really is the Little Engine That Could. Simple things like this were so ahead of its time. Just try doing this in ArcGIS, QGIS, or even Manifold 9. Sadly, it is stuck in 2010.

In 9, having no intellisense, syntax highlighting, lack of an object model to give access to things like .BackColorGreen, and unhelpful error messages like "Cannot parse query.", pretty much makes using 9 a burden.

However, 9 is still useful for opening up a .map file from 8, and exporting the data as a .geojson.

Dimitri


7,558 post(s)
#20-Apr-25 19:48

Art, it's great you can do part of what you want in 8. I contributed a lot to 8 so I'm very happy to see you like it. However, I think you're going a bit overboard. For example:

"I was able to write and entire script that does everything that I need in under 10 minutes"

More accurately, you were able to write a script that did part of your task, but not all of it.

I understand your task is to export to a geojson file a table that for each object has fields that give the background color in which that object is rendered. 8 cannot do that task. In 8 you can do part of that task by writing a short program in VBScript to get the background color used for an object, but 8 cannot do everything you require because, as you've correctly noted, it cannot export to geojson.

In contrast, 9 can do the entire task. It can add the background color used by each object into row for that object and it can also export the result to geojson. It also can do everything in SQL if you want. You don't have to dig into the object model to write code in VBScript or any other language, as you must in 8.

For those people who do like to work with colors via scripting I've given examples of various utility functions using C#, but nobody is forced to use those. Anybody can use the various SQL approaches already published in this forum.

It's great, of course, that for your task you can use both 8 and 9, doing what's more comfortable for you in 8 and doing things in 9 that only 9 can do. But it's inaccurate to say you can do all of it in 8.

As far as time required to do a job goes, an important question in terms of overall time saved is how much you win or lose with orthogonality of a modern approach like 9, at least once you learn a new system as well as you know an old system.

In the case of 9 there's orthogonality at many levels. For example, all style information is exposed as human readable text in a table in JSON style properties. The visibility of that makes such information more accessible to many more people than special cases buried within object models. The 8 object model is one of a kind, an older way of approaching such things. 9, in contrast, uses simple, standard, well-known forms for colors, such as a packed single integer representation, set forth in JSON. That's a more modern way of doing things.

The orthogonality of 9 and the visible way in which it exposes style information as ordinary text in tables anybody can read and write has other benefits.

For example, the color object in Release 8 scripting is read-only for getting channel values. You can't use it to change color. But you can do that in 9 because it's just a JSON text entry in a table that anyone can edit. That makes making changes more accessible to more people, including people without programming skills. For example, somebody who knows no programming and no SQL can easily change a color used throughout an entire project by simply doing a search and replace for that color number in the mfd_meta table.

The readability of that information allows a variety of other uses, ranging from various deft manipulations published in this forum to add alpha to colors, to simply copying/pasting or exporting the table to manipulate the JSON strings using other tools that people like, for example as is often done in Linux.

The orthogonality of the approach also makes it easy to read layer opacity in 9, because layer opacity is also exposed as a property in a table using human-readable JSON text. With 8 you have to learn yet more of a one of a kind object model to find layer opacity in the Layer object.

My own feeling is that such orthogonality is the more modern approach, while putting everything into special cases within a one of a kind object model is the older approach.

"In 9, [...] lack of an object model to give access to things like .BackColorGreen"

Are you seriously suggesting 9 doesn't have an object model? If so, I suggest a careful reading of the API documentation.

In point of fact the object model does allow access to things like the green component of the color used as background. Colors are just text in tables, and the object model allows full access to text in tables. In fact, it allows you to do pretty much whatever you want with data in tables. For anyone who's coding it's an utter triviality to get the green component of a color: g = (color >> 8) & 0xFF.

Again, that's the more modern approach, using visible information and not hiding it within secret handshake objects and structures within the internal machinery.

I certainly agree that if you happen to know 8's object model very well and you know there's a special case that fits exactly part of what you want to do, well, sure, go ahead and use it. But reliance on special cases that just happen to fit a particular task, and then asking for more and more special cases to cover more unique tasks, is asking for an unmodern approach. The modern approach is to generalize something and make it more orthogonal so you can deal with all sorts of tasks without relying on special cases.

Which brings me to

"In 9, having no intellisense, syntax highlighting",

as an example of a request to do an unmodern thing. It's not modern to want to reinvent every wheel inside an application.

In particular, in modern times people do not set out to re-invent outstanding editors like VSCode or Visual Studio Community Edition. Those provide intellisense and syntax highlighting, even for Manifold SQL (see https://georeference.org/forum/t161085.23#161086).

There are many applications that utilize scripting. The modern approach is if you want a full featured editor with things like intellisense and syntax highligting, you use a full featured editor like VSCode or the Studio editor. You don't say that each of zillions of applications that utilizes scripting should duplicate VSCode or the Studio editor.

One reason it doesn't make sense in modern times to attempt to clone VSCode is because you end up with something that has fewer features, works differently, and has fewer extensions than a standard tool like VSCode which millions of people already know.

We've discussed this before (for example, I replied to you at https://georeference.org/forum/t161085.23#163098), and I still strongly disagree that it would make sense for Manifold to duplicate VSCode within 9. If you like the features of editors like VSCode, well, just use those.

I use both Code and Studio, and it's no big deal to leave an open Code window on your desktop for use whenever you want to write code.

apo
193 post(s)
#17-Apr-25 12:42

Thanks for the Blend theory, I'll integrate that as SQL function on my side to keep it

Manifold User Community Use Agreement Copyright (C) 2007-2021 Manifold Software Limited. All rights reserved.