From 213bb2f882979788420d6450a3b4fbf0d2818396 Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Wed, 21 Feb 2024 20:52:34 +0000 Subject: [PATCH] cinera.c: Fix colouring of topic dots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The colour computed for topic dots and put into cinera_topics.css in HSL format has its lightness value modified depending on whether it's on a dark or light background. Web browsers do not tell us an element's computed colour in HSL format, but in RGB, so we would need to convert it from RGB to HSL when setting the lightness. Previously, this conversion was busted, calculating too small a value for the saturation. This commit obviates the need for any RGB→HSL conversion by writing the hue and saturation values as attributes to the elements, which the function responsible for setting the lightness may use directly. --- cinera/cinera.c | 202 +++++++++++++++++++++++++++---------------- cinera/cinera_pre.js | 67 +++++--------- 2 files changed, 150 insertions(+), 119 deletions(-) diff --git a/cinera/cinera.c b/cinera/cinera.c index 9d81bee..61c4383 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -23,7 +23,7 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 10, - .Patch = 28 + .Patch = 29 }; #define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW @@ -4172,6 +4172,68 @@ LogEdit(edit_type_id EditType, string Lineage, string EntryID, string *EntryTitl } } +#define CINERA_HSL_TRANSPARENT_HUE 65535 + +typedef struct +{ + unsigned int Hue:16; + unsigned int Saturation:8; + unsigned int Lightness:8; +} hsl_colour; + +hsl_colour +CharToColour(char Char) +{ + hsl_colour Colour; + if(Char >= 'a' && Char <= 'z') + { + Colour.Hue = (((float)Char - 'a') / ('z' - 'a') * 360); + Colour.Saturation = (((float)Char - 'a') / ('z' - 'a') * 26 + 74); + } + else if(Char >= 'A' && Char <= 'Z') + { + Colour.Hue = (((float)Char - 'A') / ('Z' - 'A') * 360); + Colour.Saturation = (((float)Char - 'A') / ('Z' - 'A') * 26 + 74); + } + else if(Char >= '0' && Char <= '9') + { + Colour.Hue = (((float)Char - '0') / ('9' - '0') * 360); + Colour.Saturation = (((float)Char - '0') / ('9' - '0') * 26 + 74); + } + else + { + Colour.Hue = 180; + Colour.Saturation = 50; + } + + return Colour; +} + +void +StringToColourHash(hsl_colour *Colour, string String) +{ + Colour->Hue = 0; + Colour->Saturation = 0; + Colour->Lightness = 74; + + for(int i = 0; i < String.Length; ++i) + { + Colour->Hue += CharToColour(String.Base[i]).Hue; + Colour->Saturation += CharToColour(String.Base[i]).Saturation; + } + + Colour->Hue = Colour->Hue % 360; + Colour->Saturation = Colour->Saturation % 26 + 74; +} + +bool +IsValidHSLColour(hsl_colour Colour) +{ + return (Colour.Hue >= 0 && Colour.Hue < 360) + && (Colour.Saturation >= 0 && Colour.Saturation <= 100) + && (Colour.Lightness >= 0 && Colour.Lightness <= 100); +} + #define SLASH 1 #define NULLTERM 1 typedef struct @@ -4326,6 +4388,7 @@ typedef struct { string Marker; string WrittenText; + hsl_colour Colour; } category_info; #define CopyString(Dest, DestSize, Format, ...) CopyString_(__LINE__, (Dest), (DestSize), (Format), ##__VA_ARGS__) @@ -5481,58 +5544,6 @@ TimecodeToDottedSeconds(v4 Timecode) return (float)Timecode.Hours * SECONDS_PER_HOUR + (float)Timecode.Minutes * SECONDS_PER_MINUTE + (float)Timecode.Seconds + (float)Timecode.Milliseconds / 1000; } -typedef struct -{ - unsigned int Hue:16; - unsigned int Saturation:8; - unsigned int Lightness:8; -} hsl_colour; - -hsl_colour -CharToColour(char Char) -{ - hsl_colour Colour; - if(Char >= 'a' && Char <= 'z') - { - Colour.Hue = (((float)Char - 'a') / ('z' - 'a') * 360); - Colour.Saturation = (((float)Char - 'a') / ('z' - 'a') * 26 + 74); - } - else if(Char >= 'A' && Char <= 'Z') - { - Colour.Hue = (((float)Char - 'A') / ('Z' - 'A') * 360); - Colour.Saturation = (((float)Char - 'A') / ('Z' - 'A') * 26 + 74); - } - else if(Char >= '0' && Char <= '9') - { - Colour.Hue = (((float)Char - '0') / ('9' - '0') * 360); - Colour.Saturation = (((float)Char - '0') / ('9' - '0') * 26 + 74); - } - else - { - Colour.Hue = 180; - Colour.Saturation = 50; - } - - return Colour; -} - -void -StringToColourHash(hsl_colour *Colour, string String) -{ - Colour->Hue = 0; - Colour->Saturation = 0; - Colour->Lightness = 74; - - for(int i = 0; i < String.Length; ++i) - { - Colour->Hue += CharToColour(String.Base[i]).Hue; - Colour->Saturation += CharToColour(String.Base[i]).Saturation; - } - - Colour->Hue = Colour->Hue % 360; - Colour->Saturation = Colour->Saturation % 26 + 74; -} - char * SanitisePunctuation(char *String) { @@ -8102,7 +8113,7 @@ BuildCredits(string HMMLFilepath, buffer *CreditsMenu, HMML_VideoMetaData *Metad } void -InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *GlobalMedia, _memory_book(category_info) *LocalMedia, string Marker) +InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *GlobalMedia, _memory_book(category_info) *LocalMedia, string Marker, hsl_colour *Colour) { medium *Medium = GetMediumFromProject(CurrentProject, Marker); @@ -8203,11 +8214,15 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_ { category_info *Src = GetPlaceInBook(LocalTopics, CategoryCount - 1); category_info *Dest = GetPlaceInBook(LocalTopics, CategoryCount); - Dest->Marker = Src->Marker; + *Dest = *Src; } category_info *New = GetPlaceInBook(LocalTopics, CategoryCount); New->Marker = Marker; + if(Colour) + { + New->Colour = *Colour; + } break; } } @@ -8218,6 +8233,10 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_ { category_info *New = GetPlaceInBook(LocalTopics, TopicIndex); New->Marker = Marker; + if(Colour) + { + New->Colour = *Colour; + } } bool MadeGlobalSpace = FALSE; @@ -8241,11 +8260,15 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_ { category_info *Src = GetPlaceInBook(GlobalTopics, CategoryCount - 1); category_info *Dest = GetPlaceInBook(GlobalTopics, CategoryCount); - Dest->Marker = Src->Marker; + *Dest = *Src; } category_info *New = GetPlaceInBook(GlobalTopics, CategoryCount); New->Marker = Marker; + if(Colour) + { + New->Colour = *Colour; + } break; } } @@ -8257,6 +8280,10 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_ { category_info *New = GetPlaceInBook(GlobalTopics, TopicIndex); New->Marker = Marker; + if(Colour) + { + New->Colour = *Colour; + } } } } @@ -8348,9 +8375,18 @@ BuildCategoryIcons(buffer *CategoryIcons, _memory_book(category_info) *LocalTopi CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base); SanitisePunctuation(SanitisedMarker); - CopyStringToBuffer(CategoryIcons, "
", + CopyStringToBuffer(CategoryIcons, + "
Marker.Length, This->Marker.Base, SanitisedMarker); + if(IsValidHSLColour(This->Colour)) + { + CopyStringToBuffer(CategoryIcons, + " data-hue=\"%u\" data-saturation=\"%u%%\"", + This->Colour.Hue, This->Colour.Saturation); + } + CopyStringToBuffer(CategoryIcons, + ">
"); } } @@ -8630,7 +8666,7 @@ BuildQuote(memory_book *Strings, quote_info *Info, string Speaker, int ID, bool } rc -GenerateTopicColours(neighbourhood *N, string Topic) +GenerateTopicColours(neighbourhood *N, string Topic, hsl_colour *Dest) { rc Result = RC_SUCCESS; // NOTE(matt): Stack-string @@ -8641,6 +8677,15 @@ GenerateTopicColours(neighbourhood *N, string Topic) medium *Medium = GetMediumFromProject(CurrentProject, Topic); if(!Medium) { + if(StringsMatch(Topic, Wrap0("nullTopic"))) + { + Dest->Hue = CINERA_HSL_TRANSPARENT_HUE; + } + else + { + StringToColourHash(Dest, Topic); + } + file Topics = {}; Topics.Path = 0; Topics.Buffer.ID = BID_TOPICS; @@ -8706,10 +8751,8 @@ GenerateTopicColours(neighbourhood *N, string Topic) } else { - hsl_colour Colour; - StringToColourHash(&Colour, Topic); WriteToFile(Topics.Handle, ".category.%s { border: 1px solid hsl(%d, %d%%, %d%%); background: hsl(%d, %d%%, %d%%); }\n", - SanitisedTopic, Colour.Hue, Colour.Saturation, Colour.Lightness, Colour.Hue, Colour.Saturation, Colour.Lightness); + SanitisedTopic, Dest->Hue, Dest->Saturation, Dest->Lightness, Dest->Hue, Dest->Saturation, Dest->Lightness); } #if DEBUG_MEM @@ -10899,7 +10942,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m { *HasFilterMenu = TRUE; } - InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("authored")); + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("authored"), NULL); hsl_colour AuthorColour; StringToColourHash(&AuthorColour, Author); // TODO(matt): That EDITION_NETWORK site database API-polling stuff @@ -10924,14 +10967,15 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m HMML_MarkerType Type = Timestamp->markers[MarkerIndex].type; if(Type == HMML_CATEGORY) { - Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker)); + hsl_colour TopicColour = {}; + Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour); if(Result == RC_SUCCESS) { if(!*HasFilterMenu) { *HasFilterMenu = TRUE; } - InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker)); + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour); CopyStringToBuffer(&IndexBuffers->Text, "%.*s", (int)StringLength(Readable), InPtr); } else @@ -11162,14 +11206,15 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m while(MarkerIndex < Timestamp->marker_count) { - Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker)); + hsl_colour TopicColour = {}; + Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour); if(Result == RC_SUCCESS) { if(!*HasFilterMenu) { *HasFilterMenu = TRUE; } - InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker)); + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour); ++MarkerIndex; } else @@ -11182,10 +11227,11 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m { if(LocalTopics.ItemCount == 0) { - Result = GenerateTopicColours(N, Wrap0("nullTopic")); + hsl_colour TopicColour = {}; + Result = GenerateTopicColours(N, Wrap0("nullTopic"), &TopicColour); if(Result == RC_SUCCESS) { - InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("nullTopic")); + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("nullTopic"), &TopicColour); } } @@ -11193,7 +11239,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m { if(LocalMedia.ItemCount == 0) { - InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, DefaultMedium->ID); + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, DefaultMedium->ID, NULL); } BuildTimestampClass(&IndexBuffers->Class, &LocalTopics, &LocalMedia, DefaultMedium->ID); @@ -11991,13 +12037,21 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF bool NullTopic = StringsMatch(This->Marker, Wrap0("nullTopic")); CopyStringToBuffer(&MenuBuffers.FilterTopics, - "
\n" - " %.*s\n" - "
\n", + " \n" + " Colour)) + { + CopyStringToBuffer(&MenuBuffers.FilterTopics, + " data-hue=\"%u\" data-saturation=\"%u%%\"", + This->Colour.Hue, This->Colour.Saturation); + } + CopyStringToBuffer(&MenuBuffers.FilterTopics, + ">%.*s\n" + " \n", NullTopic ? (int)sizeof("(null topic)")-1 : (int)This->Marker.Length, NullTopic ? "(null topic)" : This->Marker.Base); } diff --git a/cinera/cinera_pre.js b/cinera/cinera_pre.js index 5b23b3f..78aba63 100644 --- a/cinera/cinera_pre.js +++ b/cinera/cinera_pre.js @@ -551,35 +551,6 @@ BindHelp(Button, DocumentationContainer) }) } -function RGBtoHSL(colour) -{ - var rgb = colour.slice(4, -1).split(", "); - var red = rgb[0]; - var green = rgb[1]; - var blue = rgb[2]; - var min = Math.min(red, green, blue); - var max = Math.max(red, green, blue); - var chroma = max - min; - var hue = 0; - if(max == red) - { - hue = ((green - blue) / chroma) % 6; - } - else if(max == green) - { - hue = ((blue - red) / chroma) + 2; - } - else if(max == blue) - { - hue = ((red - green) / chroma) + 4; - } - - var saturation = chroma / 255 * 100; - hue = (hue * 60) < 0 ? 360 + (hue * 60) : (hue * 60); - - return [hue, saturation] -} - function getBackgroundColourRGB(element) { var Colour = getComputedStyle(element).getPropertyValue("background-color"); var depth = 0; @@ -610,28 +581,34 @@ function setTextLightness(textElement) { var textHue = textElement.getAttribute("data-hue"); var textSaturation = textElement.getAttribute("data-saturation"); - if(getBackgroundBrightness(textElement.parentNode) < 127) + if(textHue && textSaturation) { - textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)"); - } - else - { - textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 24%)"); + if(getBackgroundBrightness(textElement.parentNode) < 127) + { + textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)"); + } + else + { + textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 24%)"); + } } } function setDotLightness(topicDot) { - var Hue = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[0]; - var Saturation = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[1]; - if(getBackgroundBrightness(topicDot.parentNode) < 127) + var dotHue = topicDot.getAttribute("data-hue"); + var dotSaturation = topicDot.getAttribute("data-saturation"); + if(dotHue && dotSaturation) { - topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)"); - topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)"); - } - else - { - topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 47%)"); - topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 47%)"); + if(getBackgroundBrightness(topicDot.parentNode) < 127) + { + topicDot.style.backgroundColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)"); + topicDot.style.borderColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)"); + } + else + { + topicDot.style.backgroundColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 47%)"); + topicDot.style.borderColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 47%)"); + } } }