opengl.colors — pyFormex 1.0.5 documentation

# Source code for opengl.colors

    # $Id$
    ##
    ##  This file is part of pyFormex 1.0.5  (Sat Feb 16 10:40:32 CET 2019)
    ##  pyFormex is a tool for generating, manipulating and transforming 3D
    ##  geometrical models by sequences of mathematical operations.
    ##  Home page: http://pyformex.org
    ##  Project page:  http://savannah.nongnu.org/projects/pyformex/
    ##  Copyright 2004-2018 (C) Benedict Verhegghe (benedict.verhegghe@ugent.be)
    ##  Distributed under the GNU General Public License version 3 or later.
    ##
    ##  This program is free software: you can redistribute it and/or modify
    ##  it under the terms of the GNU General Public License as published by
    ##  the Free Software Foundation, either version 3 of the License, or
    ##  (at your option) any later version.
    ##
    ##  This program is distributed in the hope that it will be useful,
    ##  but WITHOUT ANY WARRANTY; without even the implied warranty of
    ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ##  GNU General Public License for more details.
    ##
    ##  You should have received a copy of the GNU General Public License
    ##  along with this program.  If not, see http://www.gnu.org/licenses/.
    ##
    """Playing with colors.

    This module defines some colors and color conversion functions.
    It also defines a default palette of colors.

    The following table shows the colors of the default palette, with their name,
    RGB values in 0..1 range and luminance.

    >>> for k,v in palette.items():
    ...     print("%12s = %s -> %0.3f" % (k,v,luminance(v)))
        darkgrey = (0.4, 0.4, 0.4) -> 0.133
             red = (1.0, 0.0, 0.0) -> 0.213
           green = (0.0, 1.0, 0.0) -> 0.715
            blue = (0.0, 0.0, 1.0) -> 0.072
            cyan = (0.0, 1.0, 1.0) -> 0.787
         magenta = (1.0, 0.0, 1.0) -> 0.285
          yellow = (1.0, 1.0, 0.0) -> 0.928
           white = (1.0, 1.0, 1.0) -> 1.000
           black = (0.0, 0.0, 0.0) -> 0.000
         darkred = (0.5, 0.0, 0.0) -> 0.046
       darkgreen = (0.0, 0.5, 0.0) -> 0.153
        darkblue = (0.0, 0.0, 0.5) -> 0.015
        darkcyan = (0.0, 0.5, 0.5) -> 0.169
     darkmagenta = (0.5, 0.0, 0.5) -> 0.061
      darkyellow = (0.5, 0.5, 0.0) -> 0.199
       lightgrey = (0.8, 0.8, 0.8) -> 0.604

    """
    from __future__ import absolute_import, division, print_function

    import numpy as np

    import pyformex as pf
    import pyformex.arraytools as at
    from pyformex.odict import OrderedDict

    def GLcolor(color):
        """Convert a color to an OpenGL RGB color.

        The output is a tuple of three RGB float values ranging from 0.0 to 1.0.
        The input can be any of the following:

        - a QColor
        - a string specifying the X11 name of the color
        - a hex string '#RGB' with 1 to 4 hexadecimal digits per color
        - a tuple or list of 3 integer values in the range 0..255
        - a tuple or list of 3 float values in the range 0.0..1.0

        Any other input may give unpredictable results.

        Examples:
        >>> GLcolor('red')
        (1.0, 0.0, 0.0)
        >>> GLcolor('indianred') # doctest: +ELLIPSIS
        (0.8039..., 0.3607..., 0.3607...)
        >>> GLcolor('grey90') # doctest: +ELLIPSIS
        (0.8980...,  0.8980...,  0.8980...)
        >>> print(GLcolor('#ff0000'))
        (1.0, 0.0, 0.0)
        >>> GLcolor(red)
        (1.0, 0.0, 0.0)
        >>> GLcolor([200,200,255])
        (0.7843137254901961, 0.7843137254901961, 1.0)
        >>> GLcolor([1.,1.,1.])
        (1.0, 1.0, 1.0)
        >>> GLcolor(0.6)
        (0.6, 0.6, 0.6)
        """
        from pyformex.gui import QtCore, QtGui
        col = color

        # as of Qt4.5, QtGui.Qcolor no longer raises an error if given
        # erroneous input. Therefore, we check it ourselves

        # Check if it is a palette color name
        if isinstance(col, str) and col in palette:
            col = palette[col]

        # str defined in pf.X11colors:
        if isinstance(col, str) and col in pf.X11colors:
            col = pf.X11colors[col]

        # str or QtCore.Globalcolor: convert to QColor
        if ( isinstance(col, str) or
             isinstance(col, QtCore.Qt.GlobalColor) ):
            try:
                col = QtGui.QColor(col)
            except:
                pass

        # QColor: convert to (r,g,b) tuple (0..255)
        if isinstance(col, QtGui.QColor):
            col = (col.red(), col.green(), col.blue())

        # Convert to a list and check length
        try:
            col = tuple(col)
            if len(col) == 3:
                if at.isInt(col[0]):
                    # convert int values to float
                    col = [ c/255. for c in col ]
                col = [float(c) for c in col]
                # SUCCESS !
                return tuple(col)
        except:
            pass

        # A single float value in the range 0..1 is converted to a grey value
        try:
            col = float(col)
            if col >= 0.0 and col <= 1.0:
                return grey(col)
        except:
            pass

        # No success: raise an error
        raise ValueError("GLcolor: unexpected input of type %s: %s" % (type(color), color))

    def GLcolorA(color):
        """Convert a color to an OpenGL RGB color.

        The output is a tuple of three RGB float values ranging from 0.0 to 1.0.
        The input can be any of the following:

        - a QColor
        - a string specifying the Xwindow name of the color
        - a hex string '#RGB' with 1 to 4 hexadecimal digits per color
        - a tuple or list of 3 integer values in the range 0..255
        - a tuple or list of 3 float values in the range 0.0..1.0

        Any other input may give unpredictable results.

        Example:

          >>> GLcolorA('indianred')
          array([ 0.8 ,  0.36,  0.36], dtype=float32)
          >>> print(GLcolorA('#ff0000'))
          [ 1.  0.  0.]
          >>> GLcolorA(red)
          array([ 1.,  0.,  0.], dtype=float32)
          >>> GLcolorA([200,200,255])
          array([ 0.78,  0.78,  1.  ], dtype=float32)
          >>> GLcolorA([1.,1.,1.])
          array([ 1.,  1.,  1.], dtype=float32)
          >>> GLcolorA(0.6)
          array([ 0.6,  0.6,  0.6], dtype=float32)
          >>> print(GLcolorA(['black','red','green','blue']))
          [[ 0.  0.  0.]
           [ 1.  0.  0.]
           [ 0.  1.  0.]
           [ 0.  0.  1.]]

        """
        from pyformex.gui import QtCore, QtGui
        col = color
        # as of Qt4.5, QtGui.Qcolor no longer raises an error if given
        # erroneous input. Therefore, we check it ourselves

        # Check if it is a palette color name
        if isinstance(col, str) and col in palette:
            col = palette[col]
        # str or QtCore.Globalcolor: convert to QColor
        if ( isinstance(col, str) or
             isinstance(col, QtCore.Qt.GlobalColor) ):
            try:
                col = QtGui.QColor(col)
            except:
                pass

        # QColor: convert to (r,g,b) tuple (0..255)
        if isinstance(col, QtGui.QColor):
            col = (col.red(), col.green(), col.blue())

        # A single float value in the range 0..1 is converted to a grey value
        if at.isFloat(col):
            if col >= 0.0 and col <= 1.0:
                col = grey(col)

        # Convert to an array and check length
        try:
            cola = np.array(col)
            if cola.dtype.kind in 'uif' and cola.shape[-1] == 3:
                isint = cola.dtype.kind in 'ui'
                # convert int values to float
                cola = cola.astype(np.float32)
                if isint:
                    cola /= 255.
                return cola

        except:
            pass

        if isinstance(col, (list,tuple)):
            try:
                col = GLcolorA([ GLcolorA(ci) for ci in col ])
                return col
            except:
                pass

        # No success: raise an error
        raise ValueError("GLcolorA: unexpected input of type %s: %s" % (type(color), color))

    # TODO: Should convert result to Int8 ?
    def RGBcolor(color):
        """Return an RGB (0-255) tuple for a color

        color can be anything that is accepted by GLcolor.

        Returns the corresponding RGB colors as a numpy array of type uint8
        and shape (..,3).

        Example:

          >>> RGBcolor(red)
          array([255,   0,   0], dtype=uint8)
        """
        col = np.array(GLcolor(color))*255
        return col.round().astype(np.uint8)

    def RGBAcolor(color, alpha):
        """Return an RGBA (0-255) tuple for a color and alpha value.

        color can be anything that is accepted by GLcolor.

        Returns the corresponding RGBA colors as a numpy array of type uint8
        and shape (..,4).
        """
        col = np.concatenate([np.array(GLcolor(color)), [alpha]])*255
        return col.round().astype(np.uint8)

    def WEBcolor(color):
        """Return an RGB hex string for a color

        color can be anything that is accepted by GLcolor.
        Returns the corresponding WEB color, which is a hexadecimal string
        representation of the RGB components.

        Example:

          >>> WEBcolor(red)
          '#ff0000'
        """
        col = RGBcolor(color)
        return "#%02x%02x%02x" % tuple(col)

    def colorName(color):
        """Return a string designation for the color.

        color can be anything that is accepted by GLcolor.
        In the current implementation, the returned color name is the
        WEBcolor (hexadecimal string).

        Example:

          >>> colorName('red')
          '#ff0000'
          >>> colorName('#ffddff')
          '#ffddff'
          >>> colorName([1.,0.,0.5])
          '#ff0080'
        """
        return WEBcolor(color)

    def luminance(color,gamma=True):
        """Compute the luminance of a color.

        Returns a floating point value in the range 0..1 representing the
        luminance of the color. The higher the value, the brighter the color
        appears to the human eye.

        This can be for example be used to derive a good contrasting
        foreground color to display text on a colored background.
        Values lower than 0.5 contrast well with white, larger value
        contrast better with black.

        Example:

          >>> print([ "%0.2f" % luminance(c) for c in ['black','red','green','blue']])
          ['0.00', '0.21', '0.72', '0.07']
          >>> print(luminance(['black','red','green','blue']))
          [ 0.    0.21  0.72  0.07]
        """
        color = GLcolorA(color)

        if gamma:
            color = np.where(color > 0.04045,((color+0.055)/1.055) ** 2.4,color/12.92)

        R,G,B = color[...,0], color[...,1], color[...,2]
        return 0.2126 * R + 0.7152 * G + 0.0722 * B

    # TODO: this seems pointless (unfinished)
    def createColorDict():
        from pyformex.gui import QtGui
        for c in QtGui.QColor.colorNames():
            #col = QtGui.QColor
            print("Color %s = %s" % (c, colorName(c)))

    def closestColorName(color):
        """Return the closest color name."""
        pass

    def RGBA(rgb,alpha=1.0):
        """Adds an alpha channel to an RGB color"""
        return GLcolor(rgb)+(alpha,)

    def GREY(val,alpha=1.0):
        """Returns a grey OpenGL color of given intensity (0..1)"""
        return (val, val, val, 1.0)

    def grey(i):
        return (i, i, i)

    black       = (0.0, 0.0, 0.0)
    red         = (1.0, 0.0, 0.0)
    green       = (0.0, 1.0, 0.0)
    blue        = (0.0, 0.0, 1.0)
    cyan        = (0.0, 1.0, 1.0)
    magenta     = (1.0, 0.0, 1.0)
    yellow      = (1.0, 1.0, 0.0)
    white       = (1.0, 1.0, 1.0)
    darkred     = (0.5, 0.0, 0.0)
    darkgreen   = (0.0, 0.5, 0.0)
    darkblue    = (0.0, 0.0, 0.5)
    darkcyan    = (0.0, 0.5, 0.5)
    darkmagenta = (0.5, 0.0, 0.5)
    darkyellow  = (0.5, 0.5, 0.0)

    pyformex_pink = (1.0, 0.2, 0.4)

    lightlightgrey = grey(0.9)
    lightgrey = grey(0.8)
    mediumgrey = grey(0.6)
    darkgrey = grey(0.4)

    def setPalette(colors):
        global palette
        palette = OrderedDict([ (k, GLcolor(k)) for k in colors ])

    # Set default palette
    # !! THIS IS CURRENTLY NOT USED YET
    palette = OrderedDict([ (k, globals()[k]) for k in ['darkgrey', 'red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'white', 'black', 'darkred', 'darkgreen', 'darkblue', 'darkcyan', 'darkmagenta', 'darkyellow', 'lightgrey'] ])

    # End