@@ -83,6 +83,67 @@ bool validateUint(const unsigned int &v)
8383 return true ;
8484}
8585
86+ // Utility function to validate hexadecimal color format (#RRGGBBAA)
87+ bool isValidHexColor (const char * str) {
88+ if (!str || strlen (str) != 9 ) {
89+ return false ;
90+ }
91+
92+ if (str[0 ] != ' #' ) {
93+ return false ;
94+ }
95+
96+ for (int i = 1 ; i < 9 ; i++) {
97+ char c = str[i];
98+ if (!((c >= ' 0' && c <= ' 9' ) ||
99+ (c >= ' A' && c <= ' F' ) ||
100+ (c >= ' a' && c <= ' f' ))) {
101+ return false ;
102+ }
103+ }
104+
105+ return true ;
106+ }
107+
108+ // Utility function to convert RGBA hexadecimal color string to unsigned int
109+ unsigned int hexColorToUint (const char * str) {
110+ if (!isValidHexColor (str)) {
111+ return 0 ;
112+ }
113+
114+ // Parse RGBA format: #RRGGBBAA
115+ // Extract each component separately to handle RGBA format correctly
116+ char rStr[3 ] = {str[1 ], str[2 ], ' \0 ' }; // RR
117+ char gStr [3 ] = {str[3 ], str[4 ], ' \0 ' }; // GG
118+ char bStr[3 ] = {str[5 ], str[6 ], ' \0 ' }; // BB
119+ char aStr[3 ] = {str[7 ], str[8 ], ' \0 ' }; // AA
120+
121+ unsigned int r = strtoul (rStr, nullptr , 16 );
122+ unsigned int g = strtoul (gStr , nullptr , 16 );
123+ unsigned int b = strtoul (bStr, nullptr , 16 );
124+ unsigned int a = strtoul (aStr, nullptr , 16 );
125+
126+ // Pack into ARGB format for internal use (A in bits 24-31, R in 16-23, G in 8-15, B in 0-7)
127+ // This matches the bit extraction logic used in OSD::drawText()
128+ return (a << 24 ) | (r << 16 ) | (g << 8 ) | b;
129+ }
130+
131+ // Validation function for OSD color fields that accepts both integer and hex string formats
132+ bool validateOSDColor (const unsigned int &v) {
133+ // For unsigned int values, any value is valid (same as validateUint)
134+ return true ;
135+ }
136+
137+ // Validation function for OSD color fields when parsing from JSON string
138+ bool validateOSDColorString (const char * str) {
139+ if (!str) {
140+ return false ;
141+ }
142+
143+ // Check if it's a valid hexadecimal color format
144+ return isValidHexColor (str);
145+ }
146+
86147bool validateSampleRate (const int &v)
87148{
88149 std::set<int > allowed_rates = {8000 , 16000 , 24000 , 44100 , 48000 };
@@ -279,11 +340,8 @@ std::vector<ConfigItem<int>> CFG::getIntItems()
279340 {" stream0.osd.pos_user_text_y" , stream0.osd .pos_user_text_y , OSD_AUTO_VALUE, validateInt15360},
280341 {" stream0.osd.start_delay" , stream0.osd .start_delay , 0 , [](const int &v) { return v >= 0 && v <= 5000 ; }},
281342 {" stream0.osd.time_rotation" , stream0.osd .time_rotation , 0 , validateInt360},
282- {" stream0.osd.time_transparency" , stream0.osd .time_transparency , 255 , validateInt255},
283343 {" stream0.osd.uptime_rotation" , stream0.osd .uptime_rotation , 0 , validateInt360},
284- {" stream0.osd.uptime_transparency" , stream0.osd .uptime_transparency , 255 , validateInt255},
285344 {" stream0.osd.user_text_rotation" , stream0.osd .user_text_rotation , 0 , validateInt360},
286- {" stream0.osd.user_text_transparency" , stream0.osd .user_text_transparency , 255 , validateInt255},
287345 {" stream0.rotation" , stream0.rotation , 0 , validateInt2},
288346 {" stream0.width" , stream0.width , 1920 , validateIntGe0},
289347 {" stream0.profile" , stream0.profile , 2 , validateInt2},
@@ -312,10 +370,7 @@ std::vector<ConfigItem<int>> CFG::getIntItems()
312370 {" stream1.osd.pos_user_text_y" , stream1.osd .pos_user_text_y , OSD_AUTO_VALUE, validateInt15360},
313371 {" stream1.osd.start_delay" , stream1.osd .start_delay , 0 , [](const int &v) { return v >= 0 && v <= 5000 ; }},
314372 {" stream1.osd.time_rotation" , stream1.osd .time_rotation , 0 , validateInt360},
315- {" stream1.osd.time_transparency" , stream1.osd .time_transparency , 255 , validateInt255},
316373 {" stream1.osd.uptime_rotation" , stream1.osd .uptime_rotation , 0 , validateInt360},
317- {" stream1.osd.uptime_transparency" , stream1.osd .uptime_transparency , 255 , validateInt255},
318- {" stream1.osd.user_text_transparency" , stream1.osd .user_text_transparency , 255 , validateInt255},
319374 {" stream1.osd.user_text_rotation" , stream1.osd .user_text_rotation , 0 , validateInt360},
320375 {" stream1.rotation" , stream1.rotation , 0 , validateInt2},
321376 {" stream1.width" , stream1.width , 640 , validateIntGe0},
@@ -333,10 +388,20 @@ std::vector<ConfigItem<unsigned int>> CFG::getUintItems()
333388{
334389 return {
335390 {" sensor.i2c_address" , sensor.i2c_address , 0x37 , [](const unsigned int &v) { return v <= 0x7F ; }, false , " /proc/jz/sensor/i2c_addr" },
336- {" stream0.osd.font_stroke_color" , stream0.osd .font_stroke_color , 0xFF000000 , validateUint},
337- {" stream0.osd.font_color" , stream0.osd .font_color , 0xFFFFFFFF , validateUint},
338- {" stream1.osd.font_color" , stream1.osd .font_color , 0xFFFFFFFF , validateUint},
339- {" stream1.osd.font_stroke_color" , stream1.osd .font_stroke_color , 0xFF000000 , validateUint},
391+ // Individual color settings for stream0 text elements
392+ {" stream0.osd.time_font_color" , stream0.osd .time_font_color , 0xFFFFFFFF , validateOSDColor},
393+ {" stream0.osd.time_font_stroke_color" , stream0.osd .time_font_stroke_color , 0xFF000000 , validateOSDColor},
394+ {" stream0.osd.uptime_font_color" , stream0.osd .uptime_font_color , 0xFFFFFFFF , validateOSDColor},
395+ {" stream0.osd.uptime_font_stroke_color" , stream0.osd .uptime_font_stroke_color , 0xFF000000 , validateOSDColor},
396+ {" stream0.osd.user_text_font_color" , stream0.osd .user_text_font_color , 0xFFFFFFFF , validateOSDColor},
397+ {" stream0.osd.user_text_font_stroke_color" , stream0.osd .user_text_font_stroke_color , 0xFF000000 , validateOSDColor},
398+ // Individual color settings for stream1 text elements
399+ {" stream1.osd.time_font_color" , stream1.osd .time_font_color , 0xFFFFFFFF , validateOSDColor},
400+ {" stream1.osd.time_font_stroke_color" , stream1.osd .time_font_stroke_color , 0xFF000000 , validateOSDColor},
401+ {" stream1.osd.uptime_font_color" , stream1.osd .uptime_font_color , 0xFFFFFFFF , validateOSDColor},
402+ {" stream1.osd.uptime_font_stroke_color" , stream1.osd .uptime_font_stroke_color , 0xFF000000 , validateOSDColor},
403+ {" stream1.osd.user_text_font_color" , stream1.osd .user_text_font_color , 0xFFFFFFFF , validateOSDColor},
404+ {" stream1.osd.user_text_font_stroke_color" , stream1.osd .user_text_font_stroke_color , 0xFF000000 , validateOSDColor},
340405 };
341406};
342407
@@ -508,6 +573,17 @@ void handleConfigItem(json_object *jsonConfig, ConfigItem<T> &item)
508573 item.value = static_cast <unsigned int >(val);
509574 readFromConfig = true ;
510575 }
576+ } else if (json_object_is_type (valueObj, json_type_string)) {
577+ // Check if this is an OSD color field that might be in hex format
578+ std::string path = item.path ;
579+ if (path.find (" font_color" ) != std::string::npos ||
580+ path.find (" font_stroke_color" ) != std::string::npos) {
581+ const char *str = json_object_get_string (valueObj);
582+ if (isValidHexColor (str)) {
583+ item.value = hexColorToUint (str);
584+ readFromConfig = true ;
585+ }
586+ }
511587 }
512588 } else if constexpr (std::is_same_v<T, float >) {
513589 if (json_object_is_type (valueObj, json_type_double)) {
@@ -680,6 +756,181 @@ std::vector<ConfigItem<float>> CFG::getFloatItems()
680756 };
681757};
682758
759+ void CFG::migrateOldColorSettings ()
760+ {
761+ // Helper function to combine RGB color with alpha transparency
762+ // Creates ARGB format for internal use (matches OSD::drawText() bit extraction)
763+ auto combineColorWithAlpha = [](unsigned int rgb_color, int transparency) -> unsigned int {
764+ // Extract RGB components from the input color
765+ unsigned int r = (rgb_color >> 16 ) & 0xFF ;
766+ unsigned int g = (rgb_color >> 8 ) & 0xFF ;
767+ unsigned int b = rgb_color & 0xFF ;
768+
769+ // Combine with alpha (transparency is 0-255, where 255 = opaque)
770+ unsigned int alpha = (unsigned int )transparency & 0xFF ;
771+
772+ // Pack into ARGB format for internal use (A in bits 24-31)
773+ return (alpha << 24 ) | (r << 16 ) | (g << 8 ) | b;
774+ };
775+
776+ // Check for old configuration format and migrate
777+ json_object *stream0Obj = nullptr ;
778+ json_object *stream1Obj = nullptr ;
779+
780+ if (json_object_object_get_ex (jsonConfig, " stream0" , &stream0Obj)) {
781+ json_object *osdObj = nullptr ;
782+ if (json_object_object_get_ex (stream0Obj, " osd" , &osdObj)) {
783+ // Check if old format exists (font_color + transparency fields)
784+ json_object *fontColorObj = nullptr ;
785+ json_object *fontStrokeColorObj = nullptr ;
786+ json_object *timeTransparencyObj = nullptr ;
787+ json_object *uptimeTransparencyObj = nullptr ;
788+ json_object *userTextTransparencyObj = nullptr ;
789+
790+ bool hasOldFormat = json_object_object_get_ex (osdObj, " font_color" , &fontColorObj) &&
791+ json_object_object_get_ex (osdObj, " font_stroke_color" , &fontStrokeColorObj) &&
792+ (json_object_object_get_ex (osdObj, " time_transparency" , &timeTransparencyObj) ||
793+ json_object_object_get_ex (osdObj, " uptime_transparency" , &uptimeTransparencyObj) ||
794+ json_object_object_get_ex (osdObj, " user_text_transparency" , &userTextTransparencyObj));
795+
796+ if (hasOldFormat) {
797+ unsigned int fontColor = 0xFFFFFFFF ; // Default white
798+ unsigned int fontStrokeColor = 0xFF000000 ; // Default black
799+ int timeTransparency = 255 ;
800+ int uptimeTransparency = 255 ;
801+ int userTextTransparency = 255 ;
802+
803+ // Read old values
804+ if (json_object_is_type (fontColorObj, json_type_int)) {
805+ fontColor = json_object_get_int64 (fontColorObj);
806+ } else if (json_object_is_type (fontColorObj, json_type_string)) {
807+ const char *str = json_object_get_string (fontColorObj);
808+ if (isValidHexColor (str)) {
809+ fontColor = hexColorToUint (str);
810+ }
811+ }
812+
813+ if (json_object_is_type (fontStrokeColorObj, json_type_int)) {
814+ fontStrokeColor = json_object_get_int64 (fontStrokeColorObj);
815+ } else if (json_object_is_type (fontStrokeColorObj, json_type_string)) {
816+ const char *str = json_object_get_string (fontStrokeColorObj);
817+ if (isValidHexColor (str)) {
818+ fontStrokeColor = hexColorToUint (str);
819+ }
820+ }
821+
822+ if (timeTransparencyObj && json_object_is_type (timeTransparencyObj, json_type_int)) {
823+ timeTransparency = json_object_get_int (timeTransparencyObj);
824+ }
825+ if (uptimeTransparencyObj && json_object_is_type (uptimeTransparencyObj, json_type_int)) {
826+ uptimeTransparency = json_object_get_int (uptimeTransparencyObj);
827+ }
828+ if (userTextTransparencyObj && json_object_is_type (userTextTransparencyObj, json_type_int)) {
829+ userTextTransparency = json_object_get_int (userTextTransparencyObj);
830+ }
831+
832+ // Create new individual color settings
833+ json_object_object_add (osdObj, " time_font_color" ,
834+ json_object_new_int64 (combineColorWithAlpha (fontColor, timeTransparency)));
835+ json_object_object_add (osdObj, " time_font_stroke_color" ,
836+ json_object_new_int64 (combineColorWithAlpha (fontStrokeColor, timeTransparency)));
837+ json_object_object_add (osdObj, " uptime_font_color" ,
838+ json_object_new_int64 (combineColorWithAlpha (fontColor, uptimeTransparency)));
839+ json_object_object_add (osdObj, " uptime_font_stroke_color" ,
840+ json_object_new_int64 (combineColorWithAlpha (fontStrokeColor, uptimeTransparency)));
841+ json_object_object_add (osdObj, " user_text_font_color" ,
842+ json_object_new_int64 (combineColorWithAlpha (fontColor, userTextTransparency)));
843+ json_object_object_add (osdObj, " user_text_font_stroke_color" ,
844+ json_object_new_int64 (combineColorWithAlpha (fontStrokeColor, userTextTransparency)));
845+
846+ // Remove old settings
847+ json_object_object_del (osdObj, " font_color" );
848+ json_object_object_del (osdObj, " font_stroke_color" );
849+ json_object_object_del (osdObj, " time_transparency" );
850+ json_object_object_del (osdObj, " uptime_transparency" );
851+ json_object_object_del (osdObj, " user_text_transparency" );
852+ }
853+ }
854+ }
855+
856+ // Repeat for stream1
857+ if (json_object_object_get_ex (jsonConfig, " stream1" , &stream1Obj)) {
858+ json_object *osdObj = nullptr ;
859+ if (json_object_object_get_ex (stream1Obj, " osd" , &osdObj)) {
860+ // Similar migration logic for stream1
861+ json_object *fontColorObj = nullptr ;
862+ json_object *fontStrokeColorObj = nullptr ;
863+ json_object *timeTransparencyObj = nullptr ;
864+ json_object *uptimeTransparencyObj = nullptr ;
865+ json_object *userTextTransparencyObj = nullptr ;
866+
867+ bool hasOldFormat = json_object_object_get_ex (osdObj, " font_color" , &fontColorObj) &&
868+ json_object_object_get_ex (osdObj, " font_stroke_color" , &fontStrokeColorObj) &&
869+ (json_object_object_get_ex (osdObj, " time_transparency" , &timeTransparencyObj) ||
870+ json_object_object_get_ex (osdObj, " uptime_transparency" , &uptimeTransparencyObj) ||
871+ json_object_object_get_ex (osdObj, " user_text_transparency" , &userTextTransparencyObj));
872+
873+ if (hasOldFormat) {
874+ unsigned int fontColor = 0xFFFFFFFF ;
875+ unsigned int fontStrokeColor = 0xFF000000 ;
876+ int timeTransparency = 255 ;
877+ int uptimeTransparency = 255 ;
878+ int userTextTransparency = 255 ;
879+
880+ // Read old values (similar to stream0)
881+ if (json_object_is_type (fontColorObj, json_type_int)) {
882+ fontColor = json_object_get_int64 (fontColorObj);
883+ } else if (json_object_is_type (fontColorObj, json_type_string)) {
884+ const char *str = json_object_get_string (fontColorObj);
885+ if (isValidHexColor (str)) {
886+ fontColor = hexColorToUint (str);
887+ }
888+ }
889+
890+ if (json_object_is_type (fontStrokeColorObj, json_type_int)) {
891+ fontStrokeColor = json_object_get_int64 (fontStrokeColorObj);
892+ } else if (json_object_is_type (fontStrokeColorObj, json_type_string)) {
893+ const char *str = json_object_get_string (fontStrokeColorObj);
894+ if (isValidHexColor (str)) {
895+ fontStrokeColor = hexColorToUint (str);
896+ }
897+ }
898+
899+ if (timeTransparencyObj && json_object_is_type (timeTransparencyObj, json_type_int)) {
900+ timeTransparency = json_object_get_int (timeTransparencyObj);
901+ }
902+ if (uptimeTransparencyObj && json_object_is_type (uptimeTransparencyObj, json_type_int)) {
903+ uptimeTransparency = json_object_get_int (uptimeTransparencyObj);
904+ }
905+ if (userTextTransparencyObj && json_object_is_type (userTextTransparencyObj, json_type_int)) {
906+ userTextTransparency = json_object_get_int (userTextTransparencyObj);
907+ }
908+
909+ // Create new individual color settings
910+ json_object_object_add (osdObj, " time_font_color" ,
911+ json_object_new_int64 (combineColorWithAlpha (fontColor, timeTransparency)));
912+ json_object_object_add (osdObj, " time_font_stroke_color" ,
913+ json_object_new_int64 (combineColorWithAlpha (fontStrokeColor, timeTransparency)));
914+ json_object_object_add (osdObj, " uptime_font_color" ,
915+ json_object_new_int64 (combineColorWithAlpha (fontColor, uptimeTransparency)));
916+ json_object_object_add (osdObj, " uptime_font_stroke_color" ,
917+ json_object_new_int64 (combineColorWithAlpha (fontStrokeColor, uptimeTransparency)));
918+ json_object_object_add (osdObj, " user_text_font_color" ,
919+ json_object_new_int64 (combineColorWithAlpha (fontColor, userTextTransparency)));
920+ json_object_object_add (osdObj, " user_text_font_stroke_color" ,
921+ json_object_new_int64 (combineColorWithAlpha (fontStrokeColor, userTextTransparency)));
922+
923+ // Remove old settings
924+ json_object_object_del (osdObj, " font_color" );
925+ json_object_object_del (osdObj, " font_stroke_color" );
926+ json_object_object_del (osdObj, " time_transparency" );
927+ json_object_object_del (osdObj, " uptime_transparency" );
928+ json_object_object_del (osdObj, " user_text_transparency" );
929+ }
930+ }
931+ }
932+ }
933+
683934CFG::CFG ()
684935{
685936 load ();
@@ -696,6 +947,9 @@ void CFG::load()
696947 config_loaded = readConfig ();
697948
698949 if (jsonConfig) {
950+ // Handle backward compatibility migration first
951+ migrateOldColorSettings ();
952+
699953 for (auto &item : boolItems)
700954 handleConfigItem (jsonConfig, item);
701955 for (auto &item : charItems)
0 commit comments