<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ColorField&#x2F;ColorDialog and GeUserArea – Color Space Questions]]></title><description><![CDATA[<p dir="auto">Hi SDK Team,</p>
<p dir="auto">we're currently developing a plugin that uses <code>GeUserArea</code> to display color swatches that users can sample with the ColorField's eyedropper. We've encountered some color space behaviors we'd like clarification on.</p>
<hr />
<h3>Issue 1: GetColorField returns LINEAR, DrawSetPen expects sRGB</h3>
<p dir="auto">When using a <code>ColorField</code> with a <code>GeUserArea</code>:</p>
<pre><code class="language-python"># Get color from ColorField - returns LINEAR
field_data = self.GetColorField(self.ID_COLOR_FIELD)
linear_color = field_data['color']

# Draw in GeUserArea - expects sRGB
self.DrawSetPen(linear_color)  # WRONG - appears too dark!

# Correct approach:
srgb_color = c4d.utils.TransformColor(linear_color, c4d.COLORSPACETRANSFORMATION_LINEAR_TO_SRGB)
self.DrawSetPen(srgb_color)  # Correct!
</code></pre>
<hr />
<h2>Question 1:</h2>
<p dir="auto">Is this the intended behavior? Should we always convert LINEAR→sRGB when drawing colors from ColorField/ColorDialog in a GeUserArea?</p>
<hr />
<h3>Issue 2: Eyedropper Roundtrip Doesn't Preserve Saturated Colors</h3>
<p dir="auto">We've noticed a more significant issue when using the ColorField's eyedropper to sample colors from our GeUserArea:</p>
<h3>Test Case</h3>
<ol>
<li>Draw a pure red square in GeUserArea using <code>DrawSetPen(Vector(1.0, 0, 0))</code> (sRGB)</li>
<li>Use ColorField's eyedropper to sample that red pixel</li>
<li><code>GetColorField()</code> returns a LINEAR value</li>
<li>Convert back with <code>linear_to_srgb()</code> and draw the result</li>
</ol>
<h3>Expected Result</h3>
<p dir="auto">The sampled color should match the original: sRGB <code>(1.0, 0, 0)</code></p>
<h3>Actual Result</h3>
<ul>
<li><strong>Purple</strong> (less saturated): Works correctly - roundtrip preserves the color</li>
<li><strong>Pure Red/Green/Blue</strong> (saturated): Fails - the result appears darker/desaturated</li>
</ul>
<p dir="auto">The LINEAR value returned seems incorrect for highly saturated colors, causing the sRGB conversion to not match the original. <strong>That's why the user need to be advised to sample colors in "Raw" mode?! But then again, it's not working in all cases.</strong></p>
<h3>Reproducible Demo</h3>
<p dir="auto">We've attached a demo script (<code>color_space_demo.py</code>) that demonstrates this. It shows:</p>
<ul>
<li>Test color squares (RED, GREEN, BLUE, PURPLE) drawn with known sRGB values</li>
<li>A ColorField to sample those colors</li>
<li>The result after LINEAR→sRGB conversion</li>
<li>The actual LINEAR and sRGB values for comparison</li>
</ul>
<hr />
<h2>Question 2:</h2>
<p dir="auto">Is there a known issue with the ColorDialog eyedropper and color space conversion for saturated colors? Or are we missing something in our approach? Clearly I'm a little lost at this point. <img src="http://developers.maxon.net/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f606.png?v=0b8ddba251d" class="not-responsive emoji emoji-android emoji--laughing" style="height:23px;width:auto;vertical-align:middle" title=":laughing:" alt="😆" /></p>
<hr />
<h4>Environment</h4>
<ul>
<li>Cinema 4D 2025+</li>
<li>macOS and Windows</li>
</ul>
<h4>Attached Files</h4>
<ul>
<li><code>color_space_demo.py</code> - Runnable demo in Script Manager showing both issues</li>
</ul>
<p dir="auto"><strong>Thanks for any clarification!</strong></p>
<hr />
<p dir="auto"><a href="https://www.dropbox.com/scl/fi/3i2eeljkxcc0lfmu2tfdi/CleanShot-2025-12-10-at-14.12.05.mp4?rlkey=aslxseh13skfopmi1hedg7k3l&amp;raw=1" target="_blank" rel="noopener noreferrer nofollow ugc">→ Video: color_space_demo.py</a></p>
<p dir="auto"><a href="https://www.dropbox.com/scl/fi/uahilqbsas4vsa6m4dh2m/color_space_demo.py?rlkey=ccvj505g7rmjl5jm2ddq7v6ii&amp;dl=0" target="_blank" rel="noopener noreferrer nofollow ugc">→ color_space_demo.py</a></p>
]]></description><link>http://developers.maxon.net/forum/topic/16364/colorfield-colordialog-and-geuserarea-color-space-questions</link><generator>RSS for Node</generator><lastBuildDate>Sun, 11 Oct 2026 12:28:28 GMT</lastBuildDate><atom:link href="http://developers.maxon.net/forum/topic/16364.rss" rel="self" type="application/rss+xml"/><pubDate>Wed, 10 Dec 2025 13:28:14 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to ColorField&#x2F;ColorDialog and GeUserArea – Color Space Questions on Fri, 12 Dec 2025 08:24:00 GMT]]></title><description><![CDATA[<p dir="auto">Thank you, Ferdinand. (Again!) <img src="http://developers.maxon.net/forum/assets/plugins/nodebb-plugin-emoji/emoji/android/1f642.png?v=0b8ddba251d" class="not-responsive emoji emoji-android emoji--slightly_smiling_face" style="height:23px;width:auto;vertical-align:middle" title=":)" alt="🙂" /><br />
That was exactly what I needed. It's working great now!<br />
Thanks for taking the time to answer this so thoroughly and quickly!</p>
<p dir="auto">Cheers,<br />
Lasse</p>
]]></description><link>http://developers.maxon.net/forum/post/76985</link><guid isPermaLink="true">http://developers.maxon.net/forum/post/76985</guid><dc:creator><![CDATA[lasselauch]]></dc:creator><pubDate>Fri, 12 Dec 2025 08:24:00 GMT</pubDate></item><item><title><![CDATA[Reply to ColorField&#x2F;ColorDialog and GeUserArea – Color Space Questions on Thu, 11 Dec 2025 13:20:14 GMT]]></title><description><![CDATA[<p dir="auto">Hey <a class="plugin-mentions-user plugin-mentions-a" href="/forum/user/lasselauch">@<bdi>lasselauch</bdi></a>,</p>
<p dir="auto">Thank you for reaching out to us. The issue is likely that you do not respect OCIO color management in your document. You have marked this posting as <code>S26</code>, but my hunch would be that you are using a newer version of Cinema 4D and an OCIO enabled document. The first implementation of OCIO showed up with S26 in Cinema 4D, although it was just some internal systems then such as the flag <code>DOCUMENT_COLOR_MANAGEMENT_OCIO</code> and user facing systems arrived with 2024 the earliest I think. In Python, you can only truly deal with this in 2025.0.0 and higher, as that is when we added the OCIO API to Python.</p>
<p dir="auto">When you indeed are testing this on an S26 or lower instance of Cinema 4D, the major question would be if <code>DOCUMENT_LINEARWORKFLOW</code> is enabled or not. Because you cannot just blindly convert colors.</p>
<blockquote>
<p dir="auto">Is this the intended behavior? Should we always convert LINEAR→sRGB when drawing colors from ColorField/ColorDialog in a GeUserArea?</p>
</blockquote>
<p dir="auto">The question would be what you consider here <em>this</em> ;).  But if the question is if it is intended behavior for a scene with <code>DOCUMENT_COLOR_MANAGEMENT_BASIC</code> and <code>DOCUMENT_LINEARWORKFLOW</code> enabled, to have all its scene element and parameter colors expressed as sRGB 1.0, then yes, that is the major gist of the old linear workflow. It is also intended that drawing happens in sRGB 2.2 up this day.</p>
<blockquote>
<p dir="auto">Issue 2: Eyedropper Roundtrip Doesn't Preserve Saturated Colors</p>
</blockquote>
<p dir="auto">Generally, multi question topics tend to become a mess (which is why we do not allow them). But in short: While roundtrips in the form of <code>sRGB 1.0 -&gt; sRGB 2.2 -&gt; sRGB 1.0</code> are not absolutely lossless (you are always subject to floating point precision errors), there is no giant loss by default. I am not sure what math <code>TransformColor</code> is using explicitly. A simple <code>pow(color, 2.2)</code> and <code>pow(color, 1/2.2)</code>  are the naive way to do this and the loss would be rather small. <code>TransformColor</code> might be respecting tristimulus weights which is a bit more lossy but still in a small range.</p>
<p dir="auto">OCIO roundtrips on the other hand are generally quite lossy, because ACEScg is a very wide gamut color space and converting from ACEScg to sRGB 2.2 and back can lead to significant losses in saturated colors. Some conversion paths in OCIO are even irreversible in a certain sense (depending on what color spaces you have assigned to which transform). OCIO is rather complicated to put it mildly.</p>
<blockquote>
<p dir="auto">Is there a known issue with the ColorDialog eyedropper and color space conversion for saturated colors?</p>
</blockquote>
<p dir="auto">Not that I am aware of. But you likely just ignored OCIO. And while the default OCIO Render Space of Cinema 4D (ACEScg) is in a certain sense similar to sRGB 1.0 for low saturated colors, it diverges significantly for highly saturated colors. So, your loss of saturation is likely stemming from treating an OCIO document with an ACEScg render space as sRGB 1.0.</p>
<p dir="auto">See also:</p>
<ul>
<li><a href="https://github.com/Maxon-Computer/Cinema-4D-Python-API-Examples/blob/master/scripts/04_3d_concepts/open_color_io_2025_2.py" target="_blank" rel="noopener noreferrer nofollow ugc">Python OCIO Example: open_color_io_2025_2.py</a></li>
<li><a href="https://github.com/Maxon-Computer/Cinema-4D-Python-API-Examples/blob/master/plugins/py-ocio_node_2025/py-ocio_node_2025.pyp" target="_blank" rel="noopener noreferrer nofollow ugc">Python OCIO Example: py-ocio_node_2025.pyp</a></li>
<li><a href="https://developers.maxon.net/docs/cpp/2026_1_0/article_color_management.html" target="_blank" rel="noopener noreferrer nofollow ugc">C++ Manual: Color Management</a></li>
<li><a href="https://developers.maxon.net/docs/cpp/2026_1_0/article_ocio.html" target="_blank" rel="noopener noreferrer nofollow ugc">C++ Manual: OCIO</a></li>
</ul>
<p dir="auto">Last but not least, I attached an example of what you are trying to achieve, get a color from a color gadget in a dialog and draw with it faithfully in your own user area.</p>
<p dir="auto">Cheers,<br />
Ferdinand</p>
<pre><code class="language-py">"""Demonstrates how to correctly draw with OCIO colors in a dialog.

This examples assumes that you are using Cinema 4D 2025+ with an OCIO enabled document. It will also
work in other versions and color management modes, but the point of this example is to demonstrate
OCIO color conversion for drawing in dialogs (more or less the same what is already shown in other
OCIO examples in the SDK).
"""
import c4d
from c4d import gui

class ColorArea(gui.GeUserArea):
    """Draws a color square in a custom UI element for a dialog.
    """
    def __init__(self):
        self._color: c4d.Vector = c4d.Vector(1, 0, 0) # The color to draw, this is in sRGB 2.2

    def GetMinSize(self):
        return 75, 20

    def DrawMsg(self, x1: int, y1: int, x2: int, y2: int, msg: c4d.BaseContainer) -&gt; None:
        """Draw the color of the area.
        """
        self.OffScreenOn()

        # Draw the color.
        self.DrawSetPen(self._color)
        self.DrawRectangle(x1, y1, x2, y2)

class ColorDialog(gui.GeDialog):
    """Implements a dialog that hosts a color field and chooser as well as our custom color area.
    
    The colors in the color field and chooser are in render space, so we have to convert them to sRGB
    for correct display in our user area. All three color widgets are kept in sync, i.e., changing one
    updates the others.
    """
    ID_DUMMY_ELEMENT: int = 1000
    ID_COLOR_CHOOSER: int = 1001
    ID_COLOR_FIELD: int = 1002
    ID_COLOR_AREA: int = 1003

    SPACING_BORDER: int = (5, 5, 5, 5)
    SPACING_ELEMENTS: int = (5, 5)
    DEFAULT_FLAGS: int = c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT

    def __init__(self) -&gt; None:
        """Initializes the dialog.
        """
        # Reinitializing the color area each time CreateLayout is called, could cause loosing its
        # state when this is an async dialog docked in the UI and part of a layout, as CreateLayout 
        # can be called more than once when a dialog must be reinitialized on layout changes. So, 
        # doing it in __init__ or guarding it with a check if it is already created in CreateLayout 
        # is better.
        self._color_area: ColorArea = ColorArea()

    def CreateLayout(self) -&gt; None:
        """Called by Cinema 4D to populate the dialog with elements.
        """
        self.SetTitle("Dialog Color OCIO Demo")
        # Using the same ID for dummy elements multiple times is fine, using IDs &lt; 1000 is often
        # not a good idea, as Cinema 4D usually operates in that range, and therefore an ID such 
        # as 0 can lead to issues (0 is AFIAK not actually used but better safe than sorry).
        if self.GroupBegin(self.ID_DUMMY_ELEMENT, self.DEFAULT_FLAGS, cols=1):
            self.GroupBorderSpace(*self.SPACING_BORDER)
            self.GroupSpace(*self.SPACING_ELEMENTS)

            # Add a color chooser and a color field.
            self.AddColorChooser(self.ID_COLOR_CHOOSER, c4d.BFH_LEFT)
            self.AddColorField(self.ID_COLOR_FIELD, c4d.BFH_LEFT)

            # Add our user area to display the color.
            self.AddUserArea(self.ID_COLOR_AREA, c4d.BFH_LEFT)
            self.AttachUserArea(self._color_area, self.ID_COLOR_AREA)
            self.GroupEnd()
        return True

    def InitValues(self) -&gt; bool:
        """Called by Cinema 4D to initialize the dialog values.
        """
        self.SetColors(c4d.Vector(1, 0, 0))
        return True

    def SetColors(self, color: c4d.Vector, doc: c4d.documents.BaseDocument | None = None) -&gt; None:
        """Sets the colors of all color widgets to the given render space #color.
        """
        # Just set the two color widgets first, as they expect render space colors.
        self.SetColorField(self.ID_COLOR_CHOOSER, color, 1.0, 1.0, c4d.DR_COLORFIELD_NO_BRIGHTNESS)
        self.SetColorField(self.ID_COLOR_FIELD, color, 1.0, 1.0, c4d.DR_COLORFIELD_NO_BRIGHTNESS)

        # When the call did not provide a document, use the active document.
        if not isinstance(doc, c4d.documents.BaseDocument):
            doc = c4d.documents.GetActiveDocument()

        # Check in which color mode the document is. Explicit OCIO color management exists in this 
        # form since S26 but it really only took off with 2025.
        isOCIO: bool = False
        if (c4d.GetC4DVersion() &gt;= 2025000 and
            doc[c4d.DOCUMENT_COLOR_MANAGEMENT] == c4d.DOCUMENT_COLOR_MANAGEMENT_OCIO):
            # All colors in a document are render space colors (including the color fields in 
            # dialogs). GUI drawing however still happens in sRGB space, so we need to convert
            # the render space color to sRGB for correct display. For that we need a document
            # because it contains the OCIO config and the converted which is derived from it.
            converter: c4d.modules.render.OcioConverter = doc.GetColorConverter()

            # Transform a render space color to sRGB space (there are other conversion paths
            # too, check the docs/examples on OCIO).
            color: c4d.Vector = converter.TransformColor(
                color, c4d.COLORSPACETRANSFORMATION_OCIO_RENDERING_TO_SRGB)
            
            isOCIO = True
        elif not isOCIO and doc[c4d.DOCUMENT_LINEARWORKFLOW]:
            # For non-OCIO documents (older than S26 or DOCUMENT_COLOR_MANAGEMENT_BASIC), the scene
            # element color space ('render space' in OCIO terms) can either be sRGB 2.2 or sRGB 1.0
            # (linear sRGB), depending on whether DOCUMENT_LINEARWORKFLOW is set or not. In that 
            # case, we would have to convert from gamma 1.0 to 2.2. In a modern OCIO document, we
            # could also use #converter for this, but for legacy reasons I am using here the old
            # c4d.utils function. It might be better to use the converter when this is a 2025+
            # instance of Cinema 4D. #DOCUMENT_LINEARWORKFLOW is really old, it exists at least 
            # since #R21 (I did not check earlier versions), so I am not doing another version check.
            color = c4d.utils.TransformColor(color, c4d.COLORSPACETRANSFORMATION_LINEAR_TO_SRGB)

        # Last but not least, in practice you would probably encapsulate this logic in your user
        # area, similarly to how native color elements operate just in Render Space but draw in 
        # sRGB space. For dialogs (compared to description parameters), this is a bit complicated
        # by the fact that one cannot unambiguously associate a dialog with a document from which
        # to take the color management settings. A custom GUI of a description parameter can
        # always get the node it is hosted by and its document. For dialog GUIs that is not possible.
        # So, we have to do the active document approach I showed here.

        # In a super production scenario, you would overwrite CoreMessage() of the user area or
        # dialog, to catch the active document changing, to then update the color conversion, as
        # with the document change, also the OCIO config could changed and with that its render
        # space transform.
        #
        # All in all probably a bit overkill, and I would ignore this under the banner of "who
        # cares, just reopen the dialog and you are fine". Because users will also rarely change
        # the default render space transform of ACEScg to something else.

        self._color_area._color = color
        self._color_area.Redraw()
        
    def Command(self, id: int, msg: c4d.BaseContainer) -&gt; bool:
        """Called by Cinema 4D when the user interacts with a dialog element.
        """
        if id == self.ID_COLOR_CHOOSER:
            color: c4d.Vector = self.GetColorField(self.ID_COLOR_CHOOSER)["color"]
            self.SetColors(color)
        elif id == self.ID_COLOR_FIELD:
            color: c4d.Vector = self.GetColorField(self.ID_COLOR_FIELD)["color"]
            self.SetColors(color)
        return True

# Please do not do this hack in production code. ASYNC dialogs should never be opened in a Script
# Manager script like this, because this will entail a dangling dialog instance. Use modal dialogs
# in Script Manager scripts or implement a plugin such as a command to use async dialogs.
dlg: ColorDialog = ColorDialog()
if __name__ == '__main__':
    dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=480, defaulth=400)
</code></pre>
]]></description><link>http://developers.maxon.net/forum/post/76982</link><guid isPermaLink="true">http://developers.maxon.net/forum/post/76982</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Thu, 11 Dec 2025 13:20:14 GMT</pubDate></item></channel></rss>