Higher contrast Blender Dark theme (and C# code to make one yourself)

high contrast.xml (31.0 KB)

The default “Blender Dark” lacks contrast on my monitor, so I had searched for a theme with more contrast, but without weird modifications. I could not find one. So, I created one myself. This theme is generated by modifying the “Blender Dark” using the C# code below. It did not modify anything unnecessary but just increasing the contrast of grey colours. Editing the colours in the Preference dialogue was humanly-impossible, so I wrote C# code to modify the theme XML file. The code makes dark grey colours darker by 30, and light grey colours lighter by 30. If you do not like those values, you can edit those values and run the C# code yourself.

If you want to do it yourself,

  1. In the Preferences → Themes, select “Blender Dark”, and save it by another name (by pressing the [+] button).
  2. Edit the ThemeFile constant in the C# code to the theme XML file’s path.
  3. Edit the code to change the values.
  4. Run the code.
  5. Select the new theme in Preferences → Themes.

Default “Blender Dark”

After increasing the contrast

C# code to increase the contrast.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace HigherContrast
{
    class Program
    {
        const string ThemeFile = @"C:\Users\name\AppData\Roaming\Blender Foundation\Blender\2.83\scripts\presets\interface_theme\high contrast.xml";
        const string DebugHtml = @"debug.html";
        static StreamWriter DebugOutput;
        static Regex RegexColour = new Regex(@"#([\da-f]{2}){3,4}$"); //2*3=6 and 2*4=8.

        static void Main(string[] args)
        {
            var doc = new XmlDocument();
            doc.Load(ThemeFile);
            //InitDebugOutput();
            //PrintAllColours(doc.DocumentElement);

            //Get all grey-ish (tolerance 10) colours whose brightness is below 100.
            var darkColours = GetAllGreyAttributes(doc.DocumentElement, 100, -1, 10);
            foreach (var a in darkColours)
            {
                var original = a.Value;
                a.Value = ChangeColour(original, -30); //darken by 30
            }

            //Get all grey-ish (tolerance 10) colours whose brightness is above 200.
            var lightColours = GetAllGreyAttributes(doc.DocumentElement, -1, 200, 10);
            foreach (var a in lightColours)
            {
                var original = a.Value;
                a.Value = ChangeColour(original, 30); //lighten by 30
            }

            //Save the theme file with a new name (increasing number)
            int num = 1;
            var saveFileName = string.Empty;
            do
            {
                num++;
                var dir = Path.GetDirectoryName(ThemeFile);
                var name = Path.GetFileNameWithoutExtension(ThemeFile);
                var ext = Path.GetExtension(ThemeFile);
                saveFileName = Path.Combine(dir, name) + num + ext;
            } while (File.Exists(saveFileName));

            doc.Save(saveFileName);

            //CloseOutput();
            //ShowDebugOutput();
        }

        static string ChangeColour(string colourString, int value)
        {
            Colour c = ParseARGBString(colourString);
            c.R = normalise(c.R + value);
            c.G = normalise(c.G + value);
            c.B = normalise(c.B + value);
            return c.ToHexString();

            int normalise(int value)
            {
                if (value > 255) return 255;
                else if (value < 0) return 0;
                else return value;
            }
        }

        static Colour ParseARGBString(string raw)
        {
            int a=255, r, g, b;
            bool alphaExists = false;
            int redPosition = 1;

            if (raw.Length == 9)//argb
            {
                alphaExists = true;
                a = int.Parse(raw.Substring(1, 2), System.Globalization.NumberStyles.HexNumber);
                redPosition = 3;

            }
            else if (raw.Length != 7)//rgb
            {
                Debug.Fail("length");
            }

            r = int.Parse(raw.Substring(redPosition, 2), System.Globalization.NumberStyles.HexNumber);
            g = int.Parse(raw.Substring(redPosition + 2, 2), System.Globalization.NumberStyles.HexNumber);
            b = int.Parse(raw.Substring(redPosition + 4, 2), System.Globalization.NumberStyles.HexNumber);

            if(alphaExists)
                return new Colour(r, g, b, a);
            else
                return new Colour(r, g, b);
        }

        static IEnumerable<XmlAttribute> GetAllColourAttributes(XmlNode node)
        {
            foreach (XmlNode n in node.ChildNodes)
            {
                foreach (XmlAttribute a in n.Attributes)
                {
                    var m = RegexColour.Match(a.Value);
                    if (m.Success)
                    {
                        yield return a;
                    }
                }

                foreach (XmlNode c in n.ChildNodes)
                {
                    foreach (XmlAttribute a in GetAllColourAttributes(c))
                        yield return a;
                }
            }
        }

        static IEnumerable<XmlAttribute> GetAllGreyAttributes(XmlNode node, int maximumBrightness, int minimumBrightness, int tolerance)
        {
            foreach (XmlAttribute a in GetAllColourAttributes(node))
            {
                var c = ParseARGBString(a.Value);

                int[] values = { c.R, c.G, c.B };

                var avg = values.Average();
                if ( (maximumBrightness >-1 && avg <= maximumBrightness) ||
                     (minimumBrightness >-1 && avg >= minimumBrightness) )
                {
                    var deviation = Math.Sqrt(values.Average(v => Math.Pow(v - avg, 2)));
                    if( deviation <= tolerance )
                        yield return a;
                }
            }
        }

        #region Debug features
        static void InitDebugOutput()
        {
            DebugOutput = File.CreateText(DebugHtml);
            DebugOutput.Write("<html><body>");
        }

        static void CloseDebugOutput()
        {
            DebugOutput.Write(DebugHtml, "</body></html>");
            DebugOutput.Dispose();
        }

        static void ShowDebugOutput()
        {
            Process.Start(@"cmd.exe ", @"/c " + DebugHtml);
        }

        static void PrintColourAttributes(IEnumerable<XmlAttribute> attributes)
        {
            foreach (var a in attributes)
            {
                var fullName = a.Name;
                XmlNode p = a.OwnerElement;
                while(p!=null)
                {
                    fullName = p.Name + "." + fullName;
                    p = p.ParentNode;
                    if (p?.Name == "Theme")
                        break;
                }

                var htmlColour = a.Value;
                if(a.Value.Length == 9)
                {
                    htmlColour = htmlColour.Remove(1, 2);//remove alpha
                }
                DebugOutput.WriteLine($"<div>{fullName}: {a.Value} <span style=\"display:inline-block;width:30px;height:30px;background:{htmlColour};border:1px solid\"></span></div>");
            }
        }
        #endregion
    }

    class Colour
    {
        public int A, R, G, B;
        public Colour(int r, int g, int b, int a = -1)
        {
            R = r; G = g; B = b; A = a;
        }

        public string ToHexString()
        {
            var sb = new StringBuilder();
            sb.Append("#");
            if (A != -1)
            {
                sb.AppendFormat("{0:x2}", A);
            }
            sb.AppendFormat("{0:x2}{1:x2}{2:x2}", R, G, B);
            return sb.ToString();
        }
    }
}

5 Likes

I created a Blender ID account just to reply to this. And that was before I even tried it. Now, I just compiled it, made a copy of the Blender Dark theme like you suggested, corrected the file path in the source, and ran it. It created a new theme with the same name but a “2” appended to it, and so far I really appreciate the result.

My most frequent issue with Blender Dark and any “dark” theme I found was that the scroll bar had no contrast and so did the “active” button alongside items in the Outliner. Now I can easily make out, for example, which camera is active, and it’s always obvious where the scroll bar is when I am trying to scroll (at least when I hover over it, because Blender hides it based on mouse position). It looks like the contrast of those UI portions (which always seemed to be unchanged by themes because Blender doesn’t allow changing the color of those elements) is due to the fact that the background is very dark. So, this solves my problem.

So far I’m real happy with this tool. Just a few days ago I was thinking “ugh! I should write a… a, uh, Python script, to… increase the contrast myself!” but then I looked at a theme file and I was like “uh that’s a lot of stuff” LOL. So, I was happy to go on another google search for “blender 2.93 theme high contrast” only to find that somebody wrote a program to do exactly what I had in mind but much better. Thanks!

Also, since this is blender we’re talking about, multi-platform is always a concern. Fortunately C# can run anywhere, including Linux and macOS, and Microsoft supports them pretty well, so there are opportunities to get this program compiled there too. I recall “Mono” being the way to run this stuff on those platforms, but it seems like .NET Core is multi-platform, so even better