Example #1
0
    /// Assert that the two HSLs represent the same-looking color
    /// (as opposed to having exactly the same values)
    public static void AssertNearlyEqualHSL(HSLColor lhs, HSLColor rhs)
    {
        float eps  = 1e-6f;
        float dist = (HslAsRectilinear(lhs) - HslAsRectilinear(rhs)).magnitude;

        if (!AlmostEqual(dist, 0, eps) ||
            !AlmostEqual(lhs.a, rhs.a, eps))
        {
            Assert.Fail("{0} !~ {1}", Repr(lhs), Repr(rhs));
        }
    }
Example #2
0
    // Convert from polar to rectilinear coordinates for ease of comparison
    static Vector3 HslAsRectilinear(HSLColor hsl)
    {
        float radius = .5f - Mathf.Abs(hsl.l - .5f);

        radius = radius * hsl.s;
        float angle = hsl.HueDegrees * Mathf.Deg2Rad;

        return(new Vector3(radius * Mathf.Cos(angle),
                           radius * Mathf.Sin(angle),
                           hsl.l));
    }
Example #3
0
    /// Assert that the two colors have the same field values
    /// and that orthogonal dimensions are preserved for some L and S edge cases.
    public static void AssertNearlyEqualHSLStrict(HSLColor lhs, HSLColor rhs)
    {
        float eps = 1e-4f;

        if (!HueAlmostEqual(lhs.h, rhs.h, eps) ||
            !AlmostEqual(lhs.s, rhs.s, eps) ||
            !AlmostEqual(lhs.l, rhs.l, eps) ||
            !AlmostEqual(lhs.a, rhs.a, eps))
        {
            Assert.Fail("{0} !~ {1}", Repr(lhs), Repr(rhs));
        }
    }
Example #4
0
    //
    // Utility methods
    //

    protected static string Repr(HSLColor hsl)
    {
        return(string.Format("<HSLA {0} {1} {2} {3}>", hsl.h, hsl.s, hsl.l, hsl.a));
    }
Example #5
0
        /// Unconditionally increments m_LeadingQuadIndex by 1 or 2
        private void AppendLeadingQuad(
            bool bGenerateNew, float opacity01,
            Vector3 vCenter, Vector3 vForward, Vector3 vNormal,
            Vector3 vRight, MasterBrush rMasterBrush,
            out int earliestChangedQuad)
        {
            // Get the current stroke from the MasterBrush so that quad positions and
            // orientations can be calculated.
            Vector3[] aVerts  = rMasterBrush.m_Vertices;
            Vector3[] aNorms  = rMasterBrush.m_Normals;
            Color32[] aColors = rMasterBrush.m_Colors;

            int stride = Stride;
            // Lay leading quad
            int iVertIndex = m_LeadingQuadIndex * 6;

            PositionQuad(aVerts, iVertIndex, vCenter, vForward, vRight);
            for (int i = 0; i < 6; ++i)
            {
                aNorms[iVertIndex + i] = vNormal;
            }

            earliestChangedQuad = m_LeadingQuadIndex;

            Color32 cColor = m_Color;

            cColor.a = (byte)(opacity01 * 255.0f);
            Color32 cLastColor = (iVertIndex - stride >= 0) ? aColors[iVertIndex - stride + 4] : cColor;

            aColors[iVertIndex]     = cLastColor;
            aColors[iVertIndex + 1] = cColor;
            aColors[iVertIndex + 2] = cLastColor;
            aColors[iVertIndex + 3] = cLastColor;
            aColors[iVertIndex + 4] = cColor;
            aColors[iVertIndex + 5] = cColor;

            ++m_LeadingQuadIndex;

            // Create duplicates if we have backfaces enabled.
            if (m_EnableBackfaces)
            {
                int iCurrVertIndex = m_LeadingQuadIndex * 6;
                CreateDuplicateQuad(aVerts, aNorms, m_LeadingQuadIndex, vNormal);

                Color32 backColor, lastBackColor;
                if (m_Desc.m_BackfaceHueShift == 0)
                {
                    backColor     = cColor;
                    lastBackColor = cLastColor;
                }
                else
                {
                    HSLColor hsl = (HSLColor)(Color)m_Color;
                    hsl.HueDegrees += m_Desc.m_BackfaceHueShift;
                    backColor       = (Color32)(Color)hsl;
                    lastBackColor   = (iCurrVertIndex - stride >= 0)
                        ? aColors[iCurrVertIndex - stride + 4]
                        : backColor;
                }

                aColors[iCurrVertIndex]     = lastBackColor;
                aColors[iCurrVertIndex + 1] = lastBackColor;
                aColors[iCurrVertIndex + 2] = backColor;
                aColors[iCurrVertIndex + 3] = lastBackColor;
                aColors[iCurrVertIndex + 4] = backColor;
                aColors[iCurrVertIndex + 5] = backColor;

                ++m_LeadingQuadIndex;
            }

            // Walk backward and smooth out previous quads.
            int iStripLength   = m_LeadingQuadIndex;                      // In solids
            int iSegmentLength = m_LeadingQuadIndex - m_InitialQuadIndex; // In solids

            if (m_EnableBackfaces)
            {
                iStripLength   /= 2;
                iSegmentLength /= 2;
            }

            // We don't need to smooth anything if our strip is only 1 quad.
            if (iStripLength > 1)
            {
                // Indexes for later use
                int iIndexingOffset = m_EnableBackfaces ? 2 : 1;

                int iBackQuadIndex = m_LeadingQuadIndex - (3 * iIndexingOffset);
                int iBackQuadVert  = iBackQuadIndex * 6;

                int iMidQuadIndex = m_LeadingQuadIndex - (2 * iIndexingOffset);
                int iMidQuadVert  = iMidQuadIndex * 6;

                int iFrontQuadIndex = m_LeadingQuadIndex - iIndexingOffset;
                int iFrontQuadVert  = iFrontQuadIndex * 6;

                if (iSegmentLength == 1)
                {
                    // If we've got a long strip, but this segment is only 1 quad, touch up the previous quad.
                    PositionQuad(aVerts, iMidQuadVert, m_LastQuadCenter, m_LastQuadForward, m_LastQuadRight);
                    earliestChangedQuad = Mathf.Min(earliestChangedQuad, iMidQuadIndex);

                    // Fuse back to mid if it exists and if they used to be fused.
                    if (iStripLength > 2 && m_LastSegmentLengthSolids > 1)
                    {
                        FuseQuads(aVerts, aNorms, iBackQuadVert, iMidQuadVert, bGenerateNew);
                        if (m_EnableBackfaces)
                        {
                            MakeConsistentBacksideQuad(aVerts, aNorms, iBackQuadVert);
                        }
                    }
                    else if (bGenerateNew && m_LastSegmentLengthSolids == 1)
                    {
                        // If we've got a strip longer than one quad, and this segment is only one quad, it
                        // means this is the start of a new segment. If we're beginning a new segment and
                        // our previous segment is only one quad, squash that quad to clean up artifacts.
                        PositionQuad(aVerts, iMidQuadVert, m_LastQuadCenter, Vector3.zero, Vector3.zero);
                    }
                    if (m_EnableBackfaces)
                    {
                        MakeConsistentBacksideQuad(aVerts, aNorms, iMidQuadVert);
                    }
                }
                else if (iSegmentLength == 2)
                {
                    // If we've got a long strip, but this segment is only 2 quads, just fuse.
                    FuseQuads(aVerts, aNorms, iMidQuadVert, iFrontQuadVert, bGenerateNew);

                    if (m_EnableBackfaces)
                    {
                        MakeConsistentBacksideQuad(aVerts, aNorms, iMidQuadVert);
                        MakeConsistentBacksideQuad(aVerts, aNorms, iFrontQuadVert);
                    }
                }
                else
                {
                    // Set mid quad to the midpoint of back and front quads.
                    for (int i = 0; i < 6; ++i)
                    {
                        aVerts[iMidQuadVert + i] = (aVerts[iBackQuadVert + i] + aVerts[iFrontQuadVert + i]) * 0.5f;
                    }
                    // Patch up the holes by connecting the leading edge of the back quad to the trailing
                    // edge of the mid, and do the same from mid to front.
                    FuseQuads(aVerts, aNorms, iBackQuadVert, iMidQuadVert, bGenerateNew);
                    FuseQuads(aVerts, aNorms, iMidQuadVert, iFrontQuadVert, bGenerateNew);

                    if (m_EnableBackfaces)
                    {
                        MakeConsistentBacksideQuad(aVerts, aNorms, iBackQuadVert);
                        MakeConsistentBacksideQuad(aVerts, aNorms, iMidQuadVert);
                        MakeConsistentBacksideQuad(aVerts, aNorms, iFrontQuadVert);
                    }

                    // Make sure the UVs are proper
                    UpdateUVsForQuad(iMidQuadIndex);
                }
            }
        }
        /// Returns true on success, false on failure.
        /// Failure cases are guaranteed to be identical to the failure cases of RawValueToColor
        /// Use if converting raw -> Color is lossy; otherwise, use RawValueToColor
        /// HDR color pickers might disallow conversion to HSL.
        static bool RawValueToHSLColor(ColorPickerMode mode, Vector3 raw, out HSLColor color)
        {
            const float EPSILON = 1e-5f;

            switch (mode)
            {
            case ColorPickerMode.SV_H_Rect:
                color = HSLColor.FromHSV(raw.z * HSLColor.HUE_MAX, raw.x, raw.y);
                return(true);

            case ColorPickerMode.HS_L_Polar: {
                var position = new Vector2(raw.x - 0.5f, raw.y - 0.5f) * 2;
                var radius   = position.magnitude;
                color = new HSLColor();
                if (radius > 1)
                {
                    if (radius > 1 + EPSILON)
                    {
                        return(false);
                    }
                    else
                    {
                        radius = 1;
                    }
                }
                // x direction is 0 degrees (red)
                color.HueDegrees = Mathf.Atan2(position.y, position.x) * Mathf.Rad2Deg;
                color.s          = radius;
                color.l          = raw.z;
                color.a          = 1;
                return(true);
            }

            case ColorPickerMode.SL_H_Triangle: {
                color.h = 0; // assigned later
                color.l = raw.y;
                float maxChroma = SQRT3 * ((color.l < .5f) ? color.l : (1 - color.l));
                color.s     = (maxChroma == 0) ? 0 : raw.x / maxChroma;
                color.a     = 1;
                color.Hue01 = raw.z;
                return(0 <= raw.x && raw.x <= maxChroma);
            }

            case ColorPickerMode.HL_S_Polar: {
                var position = new Vector2(raw.x - 0.5f, raw.y - 0.5f) * 2;
                var radius   = position.magnitude;
                color = new HSLColor();
                if (radius > 1)
                {
                    if (radius > 1 + EPSILON)
                    {
                        return(false);
                    }
                    else
                    {
                        radius = 1;
                    }
                }
                color.HueDegrees = Mathf.Atan2(position.y, position.x) * Mathf.Rad2Deg;
                color.l          = 1 - radius;
                color.s          = 1 - raw.z;
                color.a          = 1;
                return(true);
            }

            case ColorPickerMode.HS_LogV_Polar:
                throw new InvalidOperationException("This is a HDR mode");

            default:
                Debug.Assert(false);
                color = new HSLColor();
                return(false);
            }
        }
        /// Can never fail (unlike RawValueToColor)
        ///
        /// Note: Behavior is currently undefined if an hdr color is passed in,
        /// but the color picker cannot handle hdr.
        public static Vector3 ColorToRawValue(ColorPickerMode mode, Color rgb)
        {
            bool colorIsHdr = (rgb.r > 1 || rgb.g > 1 || rgb.b > 1);
            // If we do this a lot, maybe add this to ColorPickerInfo.hdr?
            // Or better, refactor this whole mess of code into small mode-specific classes.
            bool pickerSupportsHdr = (mode == ColorPickerMode.HS_LogV_Polar);

            if (colorIsHdr && !pickerSupportsHdr)
            {
                // Shouldn't happen except in experimental
                Debug.LogErrorFormat("Truncating HDR color to LDR");
                float h, s, v;
                Color.RGBToHSV(rgb, out h, out s, out v);
                rgb = Color.HSVToRGB(h, s, v, hdr: false);
            }

            switch (mode)
            {
            case ColorPickerMode.SV_H_Rect: {
                float h, s, v;
                Color.RGBToHSV(rgb, out h, out s, out v);
                return(new Vector3(s, v, h));
            }

            case ColorPickerMode.HS_L_Polar: {
                // H is angle, S is radius, L is depth
                HSLColor color  = (HSLColor)rgb;
                var      angle  = color.HueDegrees * Mathf.Deg2Rad;
                var      vector = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * color.s / 2 +
                                  new Vector2(0.5f, 0.5f);
                return(new Vector3(vector.x, vector.y, color.l));
            }

            case ColorPickerMode.HL_S_Polar: {
                // H is angle, (1-L) is radius, (1-S) is depth
                HSLColor color  = (HSLColor)rgb;
                var      angle  = color.HueDegrees * Mathf.Deg2Rad;
                float    radius = 1 - color.l;
                var      vector = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius / 2 +
                                  new Vector2(0.5f, 0.5f);
                return(new Vector3(vector.x, vector.y, 1 - color.s));
            }

            case ColorPickerMode.SL_H_Triangle: {
                HSLColor color = (HSLColor)rgb;
                Vector3  ret   = new Vector3();
                ret.y = color.l;
                ret.z = color.Hue01;
                float maxChroma = SQRT3 * ((color.l < .5f) ? color.l : (1 - color.l));
                ret.x = maxChroma * color.s;
                return(ret);
            }

            case ColorPickerMode.HS_LogV_Polar: {
                // H is angle, S is radius, log(V) is depth

                // This only needs to be > 0 and < the minimum ColorPickerMode.HS_LogV_Polar
                float kMinValue = 1e-7f;

                float h, s, v;
                Color.RGBToHSV(rgb, out h, out s, out v);
                Vector2 cartesian; {
                    float angle = h * (Mathf.PI * 2);
                    cartesian = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * s;
                    // convert from [-1, 1] to [0, 1]
                    cartesian = cartesian / 2 + new Vector2(.5f, .5f);
                }

                // Log-remap [2^-n, 2^n] to [-n, n]
                v += MinHDRValue;
                float slider = Mathf.Log(Mathf.Max(v, kMinValue), 2);
                slider = Mathf.Clamp(slider, kLogVMin, sm_LogVMax);
                // remap from [min, max] to [0, 1]
                slider = (slider - kLogVMin) / (sm_LogVMax - kLogVMin);
                return(new Vector3(cartesian.x, cartesian.y, slider));
            }

            default:
                return(new Vector3(1, 1, 1));
            }
        }