Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/462.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/css/42.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何仅使用CSS过滤器将黑色转换为任何给定颜色_Javascript_Css_Math_Algebra_Css Filters - Fatal编程技术网

Javascript 如何仅使用CSS过滤器将黑色转换为任何给定颜色

Javascript 如何仅使用CSS过滤器将黑色转换为任何给定颜色,javascript,css,math,algebra,css-filters,Javascript,Css,Math,Algebra,Css Filters,我的问题是:给定一个目标RGB颜色,仅使用该颜色将黑色(#000)重着色为该颜色的公式是什么 要接受答案,它需要提供一个函数(任何语言),该函数将接受目标颜色作为参数,并返回相应的CSSfilter字符串 其背景是需要在背景图像中重新存储SVG。在这种情况下,它将支持KaTeX中的某些TeX数学功能: 例子 如果目标颜色为#ffff00(黄色),则正确的解决方案是: filter: invert(100%) sepia() saturate(10000%) hue-rotate(0deg) (

我的问题是:给定一个目标RGB颜色,仅使用该颜色将黑色(
#000
)重着色为该颜色的公式是什么

要接受答案,它需要提供一个函数(任何语言),该函数将接受目标颜色作为参数,并返回相应的CSS
filter
字符串

其背景是需要在
背景图像
中重新存储SVG。在这种情况下,它将支持KaTeX中的某些TeX数学功能:

例子 如果目标颜色为
#ffff00
(黄色),则正确的解决方案是:

filter: invert(100%) sepia() saturate(10000%) hue-rotate(0deg)
()

非目标
  • 动画
  • 非CSS过滤器解决方案
  • 从黑色以外的颜色开始
  • 关心黑色以外的颜色会发生什么
迄今为止的结果
  • 强制搜索固定筛选器列表的参数:
    缺点:效率低下,仅生成16777216种可能的颜色中的一些(676248,带有
    hueRotateStep=1

  • 更快的搜索解决方案使用: 获得赏金

  • A
    投阴影
    解决方案:
    缺点:在边缘不起作用。需要非过滤器CSS更改和较小的HTML更改

通过提交非暴力解决方案,您仍然可以得到一个可接受的答案

资源
  • 如何计算
    色调旋转
    深褐色
    : Ruby实现示例:

    LUM_R = 0.2126; LUM_G = 0.7152; LUM_B = 0.0722
    HUE_R = 0.1430; HUE_G = 0.1400; HUE_B = 0.2830
    
    def clamp(num)
      [0, [255, num].min].max.round
    end
    
    def hue_rotate(r, g, b, angle)
      angle = (angle % 360 + 360) % 360
      cos = Math.cos(angle * Math::PI / 180)
      sin = Math.sin(angle * Math::PI / 180)
      [clamp(
         r * ( LUM_R  +  (1 - LUM_R) * cos  -  LUM_R * sin       ) +
         g * ( LUM_G  -  LUM_G * cos        -  LUM_G * sin       ) +
         b * ( LUM_B  -  LUM_B * cos        +  (1 - LUM_B) * sin )),
       clamp(
         r * ( LUM_R  -  LUM_R * cos        +  HUE_R * sin       ) +
         g * ( LUM_G  +  (1 - LUM_G) * cos  +  HUE_G * sin       ) +
         b * ( LUM_B  -  LUM_B * cos        -  HUE_B * sin       )),
       clamp(
         r * ( LUM_R  -  LUM_R * cos        -  (1 - LUM_R) * sin ) +
         g * ( LUM_G  -  LUM_G * cos        +  LUM_G * sin       ) +
         b * ( LUM_B  +  (1 - LUM_B) * cos  +  LUM_B * sin       ))]
    end
    
    def sepia(r, g, b)
      [r * 0.393 + g * 0.769 + b * 0.189,
       r * 0.349 + g * 0.686 + b * 0.168,
       r * 0.272 + g * 0.534 + b * 0.131]
    end
    
    请注意,上面的
    夹具
    使
    色调旋转
    功能非线性

    浏览器实现:

  • 演示:从灰度颜色获取非灰度颜色:

  • 几乎有效的公式(来自A):

    以上公式错误原因的详细解释(CSS
    hue rotate
    不是真正的色调旋转,而是线性近似):

注意:,但奖金将由戴夫回答


我知道这不是问题主体中提出的问题,当然也不是我们都在等待的问题,但有一个CSS过滤器正是这样做的:

注意事项:

  • 阴影绘制在现有内容的后面。这意味着我们必须做一些绝对定位技巧
  • 所有像素都将被同等对待,但OP说[我们不应该]“关心黑色以外的颜色会发生什么。”
  • 浏览器支持。(我不确定,只在最新的FF和chrome下测试)
/*用于隐藏原始bg的容器*/
.图标{
宽度:60px;
高度:60px;
溢出:隐藏;
}
/*内容*/
.icon.green>span{
-webkit过滤器:投阴影(60px 0px绿色);
滤镜:投影(60px 0px绿色);
}
.icon.red>span{
-webkit过滤器:投阴影(60px 0px红色);
滤镜:投影(60px 0px红色);
}
.icon>span{
-webkit过滤器:投阴影(60px 0px黑色);
滤镜:投影(60px 0px黑色);
背景位置:-100%0;
左边距:-60px;
显示:块;
宽度:61px;/*+1px用于chrome bug*/
高度:60px;
背景图片:url数据:数据:图像/svg+xml;Bas64,基础64,基础64,基础6,基础4,基础4,基础4,基础4,基础4,基础4,基础4,基础4,基础4,基础4,基础4,基础3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,30IZXZLBM9KZCIGZD0ITTYXLJUXMSWYNI4XNWMTMC43MTQTMS43MZGTMS43MJMTMY4YOTGMY4WMJYTNC42NZKGICbjLtzYLtZY2LtU5Y0Lc1Nc0WlJymi0CxYWlJI4Oc0LtZGTMS43MJMY4Y4YMY4Y4Y4Y0WlZGY4LtKg2LtKg2LtKg2LtKg2LtUg2LtUg2Lt0Lt0Lt0LtUg0LtUg0LtUg0LtUg0LtUg0LtUg0LtUtUtUtUtUtUtUtUtUtUtUtUtUtUtUtUtUtUtU未经批准的ZMJ3Icagy0xLJCWOCWWLJGWWnY0ZLJIXmiWxlJG5MY00LJUXNYWZLJI1OmTMS4ZMTGSM4ZMjCs45NdGtmy4WmJYSNC43MdJ2LtauMDizy0WlJGxWn0xLjexOcWnZLjCxWnXn0xLjexOcWlJWmIcWmS4WmS4WmJmS41MzCsNzCsNzNzNqS4xNzMmWmWmS4WmZmWmWmJ4ZmWmWmWmWmJ4YWmWmWmWmJ4WmWmWmWmWmWmWmWmWmWmWmWmWmWmWmWmWmWmWmWmWmWMC4WntusMC4WmzKsmc4Wnzesmc4Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn2Wn4Wn2Wn2Wn2Wn4Wn4Wn4Wn2Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4Wn4WnJQ5LdeumTQ4LdaumZczlDeuzy5LdaumZczadCumJg3Yzaunjismcwxljc2o0Wljm3my0WljizmSwxly0Wlju1oCwxly0Wl3n2MWlJQxOc0WlqxOwljc0WlJKsmc45NZgtmS40NjmJmmc4NjmDdG4nzzzmzzmzzzzmzzm4znznzzzzzzmzzzzzzmznznzmzzmzzzzznzzzzmzzzmzmzzzzmzznzzzmzmzzmzzzzzmzmzmzzzzmzzzmznznzzmzmznzmzmzzzWWLJI2NC0WLJQ3MYWWLJQ2NI0WLJCYMNYTMC4WMGICBJMC4XODCTMC4YMZMSC40MDMC40NJYSMC42OTLJMC4WMTYTMC4WMTYSMC4WMZETMC4WMWMWWYWYJ0NMMWYJ3NC0WLJYWWNSWXLJEWMY0xLJIXLDEUZULTE2CAGZUYZU2YZUZUZUZUZUZUZUZUZUZUZUZUZUZUZUZUZUZYKYYK4ZUZUZYKYKYKYKZUZUZUZYKYYKYKYKZYKZUZYKZYKZYKZYKZYKZYKZYKZYKZYKZYKZYKZYKZYKYKYKYKYXLJC4NSW0MY4XDJJMCW2LJG3NS0XLJC1YWXMY4WMI01JI2MSWXOC40MZZZLKXLDYLDYUMDYLDYYYY0Y0Y0Y0Y0Y0Y0Y0Y0Y0Y0XMC4XOC4XOTUTMJKY0DY0DY2DEWY0L5NSAGGINGNI40NTCSMCM0xMY0Y0Y0Y0Y0Y0LJ0ZLKY0LJ0ZLJ0ZLJ0LKY0LKY0LY0LY0DY0DY0LY0DY0DY0LY0DY0LY0DY0LY0LJ0LY0LJ0LZZZY0LK0LZZY0LY0DY0LY0LY0LY0LY0LY0LLTEUMDQTMC4ZNDITMS40NDMTMC43NDVJLTAUNDAULTAUNZUYY0WLJA5MY0WLJU2LTUYYY0WLJA5MY0WLJU2LTAUMDIZMT42MDVSNY42NTGTMYUMJCxICAGYZAUMTQTMC41NZQSMC41ODMC43OTJJJJLK3LTZUYK2LLTEZLJ4OC00L5NI0NY43ODRJ0LJJJJYYWZY3NI0NYK4MDJ044LJJJJ0YYZZZYYZZZZLZLZYM4MJJ0YYZZZZYYMJ0MZZZZZZZYYMJ0MJ0LZLZZYYYYZZZZLZLZLZLZLZLZZZ
fill: #000000
import ColorParser from 'color';

function parseColorToRgb(input: string) {
  const colorInstance = new ColorParser(input);

  return new RgbColor(
    colorInstance.red(),
    colorInstance.green(),
    colorInstance.blue(),
  );
}

function clampRgbPart(value: number): number {
  if (value > 255) {
    return 255;
  }

  if (value < 0) {
    return 0;
  }

  return value;
}

class RgbColor {
  constructor(public red: number, public green: number, public blue: number) {}

  toString() {
    return `rgb(${Math.round(this.red)}, ${Math.round(
      this.green,
    )}, ${Math.round(this.blue)})`;
  }

  set(r: number, g: number, b: number) {
    this.red = clampRgbPart(r);
    this.green = clampRgbPart(g);
    this.blue = clampRgbPart(b);
  }

  hueRotate(angle = 0) {
    angle = (angle / 180) * Math.PI;
    const sin = Math.sin(angle);
    const cos = Math.cos(angle);

    this.multiply([
      0.213 + cos * 0.787 - sin * 0.213,
      0.715 - cos * 0.715 - sin * 0.715,
      0.072 - cos * 0.072 + sin * 0.928,
      0.213 - cos * 0.213 + sin * 0.143,
      0.715 + cos * 0.285 + sin * 0.14,
      0.072 - cos * 0.072 - sin * 0.283,
      0.213 - cos * 0.213 - sin * 0.787,
      0.715 - cos * 0.715 + sin * 0.715,
      0.072 + cos * 0.928 + sin * 0.072,
    ]);
  }

  grayscale(value = 1) {
    this.multiply([
      0.2126 + 0.7874 * (1 - value),
      0.7152 - 0.7152 * (1 - value),
      0.0722 - 0.0722 * (1 - value),
      0.2126 - 0.2126 * (1 - value),
      0.7152 + 0.2848 * (1 - value),
      0.0722 - 0.0722 * (1 - value),
      0.2126 - 0.2126 * (1 - value),
      0.7152 - 0.7152 * (1 - value),
      0.0722 + 0.9278 * (1 - value),
    ]);
  }

  sepia(value = 1) {
    this.multiply([
      0.393 + 0.607 * (1 - value),
      0.769 - 0.769 * (1 - value),
      0.189 - 0.189 * (1 - value),
      0.349 - 0.349 * (1 - value),
      0.686 + 0.314 * (1 - value),
      0.168 - 0.168 * (1 - value),
      0.272 - 0.272 * (1 - value),
      0.534 - 0.534 * (1 - value),
      0.131 + 0.869 * (1 - value),
    ]);
  }

  saturate(value = 1) {
    this.multiply([
      0.213 + 0.787 * value,
      0.715 - 0.715 * value,
      0.072 - 0.072 * value,
      0.213 - 0.213 * value,
      0.715 + 0.285 * value,
      0.072 - 0.072 * value,
      0.213 - 0.213 * value,
      0.715 - 0.715 * value,
      0.072 + 0.928 * value,
    ]);
  }

  multiply(matrix: number[]) {
    const newR = clampRgbPart(
      this.red * matrix[0] + this.green * matrix[1] + this.blue * matrix[2],
    );
    const newG = clampRgbPart(
      this.red * matrix[3] + this.green * matrix[4] + this.blue * matrix[5],
    );
    const newB = clampRgbPart(
      this.red * matrix[6] + this.green * matrix[7] + this.blue * matrix[8],
    );
    this.red = newR;
    this.green = newG;
    this.blue = newB;
  }

  brightness(value = 1) {
    this.linear(value);
  }

  contrast(value = 1) {
    this.linear(value, -(0.5 * value) + 0.5);
  }

  linear(slope = 1, intercept = 0) {
    this.red = clampRgbPart(this.red * slope + intercept * 255);
    this.green = clampRgbPart(this.green * slope + intercept * 255);
    this.blue = clampRgbPart(this.blue * slope + intercept * 255);
  }

  invert(value = 1) {
    this.red = clampRgbPart((value + (this.red / 255) * (1 - 2 * value)) * 255);
    this.green = clampRgbPart(
      (value + (this.green / 255) * (1 - 2 * value)) * 255,
    );
    this.blue = clampRgbPart(
      (value + (this.blue / 255) * (1 - 2 * value)) * 255,
    );
  }

  applyFilters(filters: Filters) {
    this.set(0, 0, 0);
    this.invert(filters[0] / 100);
    this.sepia(filters[1] / 100);
    this.saturate(filters[2] / 100);
    this.hueRotate(filters[3] * 3.6);
    this.brightness(filters[4] / 100);
    this.contrast(filters[5] / 100);
  }

  hsl(): HSLData {
    // Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA.
    const r = this.red / 255;
    const g = this.green / 255;
    const b = this.blue / 255;
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    let h: number,
      s: number,
      l = (max + min) / 2;

    if (max === min) {
      h = s = 0;
    } else {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch (max) {
        case r:
          h = (g - b) / d + (g < b ? 6 : 0);
          break;

        case g:
          h = (b - r) / d + 2;
          break;

        case b:
          h = (r - g) / d + 4;
          break;
      }
      h! /= 6;
    }

    return {
      h: h! * 100,
      s: s * 100,
      l: l * 100,
    };
  }
}

interface HSLData {
  h: number;
  s: number;
  l: number;
}

interface ColorFilterSolveResult {
  loss: number;
  filters: Filters;
}

const reusedColor = new RgbColor(0, 0, 0);

function formatFilterValue(value: number, multiplier = 1) {
  return Math.round(value * multiplier);
}

type Filters = [
  invert: number,
  sepia: number,
  saturate: number,
  hueRotate: number,
  brightness: number,
  contrast: number,
];

function convertFiltersListToCSSFilter(filters: Filters) {
  function fmt(idx: number, multiplier = 1) {
    return Math.round(filters[idx] * multiplier);
  }
  const [invert, sepia, saturate, hueRotate, brightness, contrast] = filters;
  return `filter: invert(${formatFilterValue(
    invert,
  )}%) sepia(${formatFilterValue(sepia)}%) saturate(${formatFilterValue(
    saturate,
  )}%) hue-rotate(${formatFilterValue(
    hueRotate,
    3.6,
  )}deg) brightness(${formatFilterValue(
    brightness,
  )}%) contrast(${formatFilterValue(contrast)}%);`;
}

function calculateLossForFilters(
  filters: Filters,
  targetColor: RgbColor,
  targetHSL: HSLData,
) {
  reusedColor.applyFilters(filters);
  const actualHSL = reusedColor.hsl();

  return (
    Math.abs(reusedColor.red - targetColor.red) +
    Math.abs(reusedColor.green - targetColor.green) +
    Math.abs(reusedColor.blue - targetColor.blue) +
    Math.abs(actualHSL.h - targetHSL.h) +
    Math.abs(actualHSL.s - targetHSL.s) +
    Math.abs(actualHSL.l - targetHSL.l)
  );
}



export function solveColor(input: string) {
  const targetColor = parseColorToRgb(input);
  const targetHSL = targetColor.hsl();

  function improveInitialSolveResult(initialResult: ColorFilterSolveResult) {
    const A = initialResult.loss;
    const c = 2;
    const A1 = A + 1;
    const a: Filters = [
      0.25 * A1,
      0.25 * A1,
      A1,
      0.25 * A1,
      0.2 * A1,
      0.2 * A1,
    ];
    return findColorFilters(A, a, c, initialResult.filters, 500);
  }

  function findColorFilters(
    initialLoss: number,
    filters: Filters,
    c: number,
    values: Filters,
    iterationsCount: number,
  ): ColorFilterSolveResult {
    const alpha = 1;
    const gamma = 0.16666666666666666;

    let best = null;
    let bestLoss = Infinity;
    const deltas = new Array(6);
    const highArgs = new Array(6) as Filters;
    const lowArgs = new Array(6) as Filters;

    for (
      let iterationIndex = 0;
      iterationIndex < iterationsCount;
      iterationIndex++
    ) {
      const ck = c / Math.pow(iterationIndex + 1, gamma);
      for (let i = 0; i < 6; i++) {
        deltas[i] = Math.random() > 0.5 ? 1 : -1;
        highArgs[i] = values[i] + ck * deltas[i];
        lowArgs[i] = values[i] - ck * deltas[i];
      }

      const lossDiff =
        calculateLossForFilters(highArgs, targetColor, targetHSL) -
        calculateLossForFilters(lowArgs, targetColor, targetHSL);

      for (let i = 0; i < 6; i++) {
        const g = (lossDiff / (2 * ck)) * deltas[i];
        const ak =
          filters[i] / Math.pow(initialLoss + iterationIndex + 1, alpha);
        values[i] = fix(values[i] - ak * g, i);
      }

      const loss = calculateLossForFilters(values, targetColor, targetHSL);
      if (loss < bestLoss) {
        best = values.slice(0) as Filters;
        bestLoss = loss;
      }
    }
    return { filters: best!, loss: bestLoss };

    function fix(value: number, idx: number) {
      let max = 100;
      if (idx === 2 /* saturate */) {
        max = 7500;
      } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) {
        max = 200;
      }

      if (idx === 3 /* hue-rotate */) {
        if (value > max) {
          value %= max;
        } else if (value < 0) {
          value = max + (value % max);
        }
      } else if (value < 0) {
        value = 0;
      } else if (value > max) {
        value = max;
      }
      return value;
    }
  }

  function solveInitial(): ColorFilterSolveResult {
    const A = 5;
    const c = 15;
    const a: Filters = [60, 180, 18000, 600, 1.2, 1.2];

    let best: ColorFilterSolveResult = {
      loss: Infinity,
      filters: [0, 0, 0, 0, 0, 0],
    };
    for (let i = 0; best.loss > 25 && i < 3; i++) {
      const initial: Filters = [50, 20, 3750, 50, 100, 100];
      const result = findColorFilters(A, a, c, initial, 1000);
      if (result.loss < best.loss) {
        best = result;
      }
    }
    return best;
  }

  const result = improveInitialSolveResult(solveInitial());

  return convertFiltersListToCSSFilter(result.filters)
}

const colorFiltersCache = new Map<string, string>();

export function cachedSolveColor(input: string) {
  const existingResult = colorFiltersCache.get(input);

  if (existingResult) {
    return existingResult;
  }

  const newResult = solveColor(input);

  colorFiltersCache.set(input, newResult);

  return newResult;
}
@use "sass:math";

@mixin recolor($color: #000) {
  $r: math.div(red($color), 255);
  $g: math.div(green($color), 255);
  $b: math.div(blue($color), 255);
  $a: alpha($color);
 
  // grayscale fallback if SVG from data url is not supported
  $lightness: lightness($color);
  filter: saturate(0%) brightness(0%) invert($lightness) opacity($a);

  // color filter
  $svg-filter-id: "recolor";
  filter: url('data:image/svg+xml;utf8,\
  <svg xmlns="http://www.w3.org/2000/svg">\
    <filter id="#{$svg-filter-id}" color-interpolation-filters="sRGB">\
      <feColorMatrix type="matrix" values="\
       0 0 0 0 #{$r}\
       0 0 0 0 #{$g}\
       0 0 0 0 #{$b}\
       0 0 0 #{$a} 0\
      "/>\
    </filter>\
  </svg>\
  ##{$svg-filter-id}');
}
// applied with
@include recolor($arbitrary-color);