From c8ee451a224c94696416786f6eda9c989e502a1e Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Tue, 28 Oct 2025 22:05:09 -0400 Subject: [PATCH] Fix parsing for alpha with percent values. Add support for percent values in rgb --- CHANGELOG.md | 1 + src/color.cc | 65 +++++++++++++++++++++++++++++---------------- test/canvas.test.js | 21 +++++++++++++++ 3 files changed, 64 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59fad2f10..3d65bcbc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ project adheres to [Semantic Versioning](http://semver.org/). * Fix error message HTTP response status code in image src setter * `roundRect()` shape incorrect when radii were large relative to rectangle size (#2400) * Reject loadImage when src is null or invalid (#2304) +* Add support for percentage in rgb and rgba parsing. Fix bug with parsing for alpha and percentages. 3.2.0 ================== diff --git a/src/color.cc b/src/color.cc index f82629460..9308d8645 100644 --- a/src/color.cc +++ b/src/color.cc @@ -9,7 +9,6 @@ #include #include #include - // Compatibility with Visual Studio versions prior to VS2015 #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf _snprintf @@ -153,6 +152,32 @@ wrap_float(T value, T limit) { // return (value % limit + limit) % limit; // } +/* + * Parse and clip a percentage value. Returns a float in the range [0, 1]. + */ + + +static bool +check_percentage(const char* str) { + while (*str && *str != '%' && *str != ',' && *str != ' ' && *str != '/') ++str; + return *str == '%'; +} + +static bool +parse_clipped_percentage(const char** pStr, float *pFraction) { + float percentage; + bool result = parse_css_number(pStr,&percentage); + const char*& str = *pStr; + if (result) { + if (*str == '%') { + ++str; + *pFraction = clip(percentage, 0.0f, 100.0f) / 100.0f; + return result; + } + } + return false; +} + /* * Parse color channel value */ @@ -160,7 +185,12 @@ wrap_float(T value, T limit) { static bool parse_rgb_channel(const char** pStr, uint8_t *pChannel) { float f_channel; - if (parse_css_number(pStr, &f_channel)) { + bool percentage = check_percentage(*pStr); + if(percentage && parse_clipped_percentage(pStr, &f_channel)) { + int channel = (int) ceil(255 * f_channel); + *pChannel = channel; + return true; + } else if (parse_css_number(pStr, &f_channel)) { int channel = (int) ceil(f_channel); *pChannel = clip(channel, 0, 255); return true; @@ -182,24 +212,6 @@ parse_degrees(const char** pStr, float *pDegrees) { return false; } -/* - * Parse and clip a percentage value. Returns a float in the range [0, 1]. - */ - -static bool -parse_clipped_percentage(const char** pStr, float *pFraction) { - float percentage; - bool result = parse_css_number(pStr,&percentage); - const char*& str = *pStr; - if (result) { - if (*str == '%') { - ++str; - *pFraction = clip(percentage, 0.0f, 100.0f) / 100.0f; - return result; - } - } - return false; -} /* * Macros to help with parsing inside rgba_from_*_string @@ -231,13 +243,15 @@ parse_clipped_percentage(const char** pStr, float *pFraction) { #define ALPHA(NAME) \ if (*str >= '1' && *str <= '9') { \ NAME = 0; \ - float n = .1f; \ while(*str >='0' && *str <= '9') { \ - NAME += (*str - '0') * n; \ + NAME = NAME * 10 + (*str - '0'); \ str++; \ } \ while(*str == ' ')str++; \ - if(*str != '%') { \ + if(*str == '%') { \ + NAME *= 0.01f; \ + ++str; \ + } else { \ NAME = 1; \ } \ } else { \ @@ -253,6 +267,11 @@ parse_clipped_percentage(const char** pStr, float *pFraction) { NAME += (*str++ - '0') * n; \ n *= .1f; \ } \ + while(*str == ' ')str++; \ + if(*str == '%') { \ + NAME *= 0.01f; \ + ++str; \ + } \ } \ } \ do {} while (0) // require trailing semicolon diff --git a/test/canvas.test.js b/test/canvas.test.js index 4655c31c7..5a510aeea 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -232,6 +232,27 @@ describe('Canvas', function () { ctx.fillStyle = 'rgb( 255, 300.09, 90, 40%)' assert.equal('rgba(255, 255, 90, 0.40)', ctx.fillStyle) + + ctx.fillStyle = 'rgb( 255, 100%, 50%, 60%)' + assert.equal('rgba(255, 255, 128, 0.60)', ctx.fillStyle) + + ctx.fillStyle = 'rgb( 255, 20%, 90, 70%)' + assert.equal('rgba(255, 51, 90, 0.70)', ctx.fillStyle) + + ctx.fillStyle = 'rgb( 100%, 300%, 90, 50%)' + assert.equal('rgba(255, 255, 90, 0.50)', ctx.fillStyle) + + ctx.fillStyle = 'rgb( 100%, 300.09, 90%, 0)' + assert.equal('rgba(255, 255, 230, 0.00)', ctx.fillStyle) + + ctx.fillStyle = 'rgb( 100%, 99%, 80% 50%)' + assert.equal('rgba(255, 253, 204, 0.50)', ctx.fillStyle) + + ctx.fillStyle = 'rgb( 100%, 99.99%, 40% / 50%)' + assert.equal('rgba(255, 255, 102, 0.50)', ctx.fillStyle) + + ctx.fillStyle = 'rgb( 100%, 33.33%, 66.66%, 2%)' + assert.equal('rgba(255, 85, 170, 0.02)', ctx.fillStyle) // hsl / hsla tests ctx.fillStyle = 'hsl(0, 0%, 0%)'