केवल सीएसएस फिल्टर का उपयोग करके किसी भी दिए गए रंग में काले को कैसे परिवर्तित किया जाए


114

मेरा प्रश्न है: एक लक्ष्य RGB रंग दिया #000गया है, उस रंग में केवल CSS फ़िल्टर का उपयोग करके काले रंग को फिर से रंगने का क्या सूत्र है ?

स्वीकार किए जाने वाले उत्तर के लिए, इसे एक फ़ंक्शन (किसी भी भाषा में) प्रदान करना होगा जो लक्ष्य रंग को एक तर्क के रूप में स्वीकार करेगा और संबंधित सीएसएस filterस्ट्रिंग लौटाएगा ।

इसके लिए संदर्भ एक एसवीजी के अंदर पुनरावृत्ति करने की आवश्यकता है background-image। इस मामले में, यह KaTeX: https://github.com/Khan/KaTeX/issues/587 : कुछ TeX गणित सुविधाओं का समर्थन करना है ।

उदाहरण

यदि लक्ष्य रंग #ffff00(पीला) है, तो एक सही समाधान है:

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

( डेमो )

गैर लक्ष्यों

  • एनीमेशन।
  • गैर सीएसएस-फिल्टर समाधान।
  • काले रंग के अलावा अन्य रंग से शुरू।
  • काले रंग के अलावा अन्य रंगों का क्या होता है, इसकी देखभाल करना।

अब तक के परिणाम

  • एक निश्चित फिल्टर सूची के मापदंडों के लिए ब्रूट-बल खोज: https://stackoverflow.com/a/43959856/181228
    विपक्ष: अक्षम, केवल 16,777,216 संभव रंगों (676,248 के साथ hueRotateStep=1) में से कुछ उत्पन्न करता है ।

  • SPSA का उपयोग करके एक तेज़ खोज समाधान : https://stackoverflow.com/a/43960991/181228 बाउंटी द्वारा सम्मानित किया गया

  • एक drop-shadowसमाधान: https://stackoverflow.com/a/43959853/181228
    विपक्ष: एज पर काम नहीं करता है। गैर- filterCSS परिवर्तन और मामूली HTML परिवर्तनों की आवश्यकता है।

आप अभी भी एक गैर जानवर बल समाधान प्रस्तुत करके एक स्वीकृत जवाब प्राप्त कर सकते हैं!

साधन

  • कैसे hue-rotateऔर कैसे sepiaगणना की जाती है: https://stackoverflow.com/a/29521147/181228 उदाहरण रूबी कार्यान्वयन:

    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

    ध्यान दें कि clampउपरोक्त hue-rotateफ़ंक्शन को गैर-रैखिक बनाता है ।

    ब्राउज़र कार्यान्वयन: क्रोमियम , फ़ायरफ़ॉक्स

  • डेमो: ग्रेस्केल रंग से गैर-ग्रेस्केल रंग प्राप्त करना: https://stackoverflow.com/a/25524145/181228

  • एक सूत्र जो लगभग (एक समान प्रश्न से ) काम करता है :
    https://stackoverflow.com/a/29958459/181228

    उपरोक्त सूत्र क्यों गलत है, इसकी विस्तृत व्याख्या (CSS hue-rotateएक सच्चा वर्ण रोटेशन नहीं है लेकिन रैखिक सन्निकटन है):
    https://stackoverflow.com/a/19325417/2441511


तो आप LERP # 000000 से लेकर #RRGGBB करना चाहते हैं? (सिर्फ स्पष्ट करते हुए)
ज़ेग

1
हाँ मीठा - सिर्फ यह स्पष्ट करना कि आप समाधान में एक संक्रमण को शामिल नहीं करना चाहते थे।
ज़ेज़

1
हो सकता है कि एक मिश्रण मोड आपके लिए काम करेगा? आप आसानी से किसी भी रंग को काला परिवर्तित कर सकते हैं ... लेकिन मैं तुम्हें क्या हासिल करना चाहते का वैश्विक चित्र नहीं मिलता है
Vals

1
@glebm तो आपको किसी भी रंग में काले रंग को चालू करने और css का उपयोग करके इसे लागू करने के लिए एक सूत्र (किसी भी विधि का उपयोग करके) को खोजने की आवश्यकता है?
ProllyGeek

2
@ProllyGeek हाँ। एक अन्य बाधा जिसका मुझे उल्लेख करना चाहिए कि परिणामी सूत्र 5GiB तालिका का एक क्रूर बल लुकअप नहीं हो सकता है (यह एक वेबपेज पर उदाहरण जावास्क्रिप्ट से उपयोगी होना चाहिए)।
ग्लीबम

जवाबों:


148

@ डेव इस (वर्किंग कोड के साथ) का उत्तर पोस्ट करने वाले पहले व्यक्ति थे , और उनका जवाब बेशर्म कॉपी और मेरे लिए प्रेरणा चिपकाने का एक अमूल्य स्रोत रहा है। यह पोस्ट @ डेव के उत्तर को समझाने और परिष्कृत करने के प्रयास के रूप में शुरू हुई, लेकिन तब से यह स्वयं के उत्तर में विकसित हुई है।

मेरा तरीका काफी तेज है। यादृच्छिक रूप से उत्पन्न RGB रंगों पर एक jsPerf बेंचमार्क के अनुसार , @ डेव का एल्गोरिथ्म 600 ms में चलता है , जबकि मेरा 30 ms में चलता है । यह निश्चित रूप से मायने रखता है, उदाहरण के लिए लोड समय में, जहां गति महत्वपूर्ण है।

इसके अलावा, कुछ रंगों के लिए, मेरा एल्गोरिथ्म बेहतर प्रदर्शन करता है:

  • के लिए rgb(0,255,0), @ डेव का उत्पादन rgb(29,218,34)और उत्पादन होता हैrgb(1,255,0)
  • के लिए rgb(0,0,255), @ डेव का उत्पादन rgb(37,39,255)और मेरा उत्पादन होता हैrgb(5,6,255)
  • के लिए rgb(19,11,118), @ डेव का उत्पादन rgb(36,27,102)और मेरा उत्पादन होता हैrgb(20,11,112)

डेमो

"use strict";

class Color {
    constructor(r, g, b) { this.set(r, g, b); }
    toString() { return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`; }

    set(r, g, b) {
        this.r = this.clamp(r);
        this.g = this.clamp(g);
        this.b = this.clamp(b);
    }

    hueRotate(angle = 0) {
        angle = angle / 180 * Math.PI;
        let sin = Math.sin(angle);
        let 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.140, 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) {
        let newR = this.clamp(this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]);
        let newG = this.clamp(this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]);
        let newB = this.clamp(this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]);
        this.r = newR; this.g = newG; this.b = newB;
    }

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

    linear(slope = 1, intercept = 0) {
        this.r = this.clamp(this.r * slope + intercept * 255);
        this.g = this.clamp(this.g * slope + intercept * 255);
        this.b = this.clamp(this.b * slope + intercept * 255);
    }

    invert(value = 1) {
        this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255);
        this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255);
        this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255);
    }

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

        if(max === min) {
            h = s = 0;
        } else {
            let 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
        };
    }

    clamp(value) {
        if(value > 255) { value = 255; }
        else if(value < 0) { value = 0; }
        return value;
    }
}

class Solver {
    constructor(target) {
        this.target = target;
        this.targetHSL = target.hsl();
        this.reusedColor = new Color(0, 0, 0); // Object pool
    }

    solve() {
        let result = this.solveNarrow(this.solveWide());
        return {
            values: result.values,
            loss: result.loss,
            filter: this.css(result.values)
        };
    }

    solveWide() {
        const A = 5;
        const c = 15;
        const a = [60, 180, 18000, 600, 1.2, 1.2];

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

    solveNarrow(wide) {
        const A = wide.loss;
        const c = 2;
        const A1 = A + 1;
        const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1];
        return this.spsa(A, a, c, wide.values, 500);
    }

    spsa(A, a, c, values, iters) {
        const alpha = 1;
        const gamma = 0.16666666666666666;

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

        for(let k = 0; k < iters; k++) {
            let ck = c / Math.pow(k + 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];
            }

            let lossDiff = this.loss(highArgs) - this.loss(lowArgs);
            for(let i = 0; i < 6; i++) {
                let g = lossDiff / (2 * ck) * deltas[i];
                let ak = a[i] / Math.pow(A + k + 1, alpha);
                values[i] = fix(values[i] - ak * g, i);
            }

            let loss = this.loss(values);
            if(loss < bestLoss) { best = values.slice(0); bestLoss = loss; }
        } return { values: best, loss: bestLoss };

        function fix(value, idx) {
            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 = value % max; }
                else if(value < 0) { value = max + value % max; }
            } else if(value < 0) { value = 0; }
            else if(value > max) { value = max; }
            return value;
        }
    }

    loss(filters) { // Argument is array of percentages.
        let color = this.reusedColor;
        color.set(0, 0, 0);

        color.invert(filters[0] / 100);
        color.sepia(filters[1] / 100);
        color.saturate(filters[2] / 100);
        color.hueRotate(filters[3] * 3.6);
        color.brightness(filters[4] / 100);
        color.contrast(filters[5] / 100);

        let colorHSL = color.hsl();
        return Math.abs(color.r - this.target.r)
            + Math.abs(color.g - this.target.g)
            + Math.abs(color.b - this.target.b)
            + Math.abs(colorHSL.h - this.targetHSL.h)
            + Math.abs(colorHSL.s - this.targetHSL.s)
            + Math.abs(colorHSL.l - this.targetHSL.l);
    }

    css(filters) {
        function fmt(idx, multiplier = 1) { return Math.round(filters[idx] * multiplier); }
        return `filter: invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%);`;
    }
}

$("button.execute").click(() => {
    let rgb = $("input.target").val().split(",");
    if (rgb.length !== 3) { alert("Invalid format!"); return; }

    let color = new Color(rgb[0], rgb[1], rgb[2]);
    let solver = new Solver(color);
    let result = solver.solve();

    let lossMsg;
    if (result.loss < 1) {
        lossMsg = "This is a perfect result.";
    } else if (result.loss < 5) {
        lossMsg = "The is close enough.";
    } else if(result.loss < 15) {
        lossMsg = "The color is somewhat off. Consider running it again.";
    } else {
        lossMsg = "The color is extremely off. Run it again!";
    }

    $(".realPixel").css("background-color", color.toString());
    $(".filterPixel").attr("style", result.filter);
    $(".filterDetail").text(result.filter);
    $(".lossDetail").html(`Loss: ${result.loss.toFixed(1)}. <b>${lossMsg}</b>`);
});
.pixel {
    display: inline-block;
    background-color: #000;
    width: 50px;
    height: 50px;
}

.filterDetail {
    font-family: "Consolas", "Menlo", "Ubuntu Mono", monospace;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<input class="target" type="text" placeholder="r, g, b" value="250, 150, 50" />
<button class="execute">Compute Filters</button>

<p>Real pixel, color applied through CSS <code>background-color</code>:</p>
<div class="pixel realPixel"></div>

<p>Filtered pixel, color applied through CSS <code>filter</code>:</p>
<div class="pixel filterPixel"></div>

<p class="filterDetail"></p>
<p class="lossDetail"></p>


प्रयोग

let color = new Color(0, 255, 0);
let solver = new Solver(color);
let result = solver.solve();
let filterCSS = result.css;

व्याख्या

हम कुछ जावास्क्रिप्ट लिखना शुरू करेंगे।

"use strict";

class Color {
    constructor(r, g, b) {
        this.r = this.clamp(r);
        this.g = this.clamp(g);
        this.b = this.clamp(b);
    } toString() { return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`; }

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

        if(max === min) {
            h = s = 0;
        } else {
            let 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
        };
    }

    clamp(value) {
        if(value > 255) { value = 255; }
        else if(value < 0) { value = 0; }
        return value;
    }
}

class Solver {
    constructor(target) {
        this.target = target;
        this.targetHSL = target.hsl();
    }

    css(filters) {
        function fmt(idx, multiplier = 1) { return Math.round(filters[idx] * multiplier); }
        return `filter: invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%);`;
    }
}

स्पष्टीकरण:

  • Colorवर्ग एक आरजीबी रंग का प्रतिनिधित्व करता है।
    • आईटी इस toString() कार्य सीएसएस rgb(...)रंग स्ट्रिंग में रंग लौटाता है ।
    • आईटी इस hsl() कार्य एचएसएल में परिवर्तित रंग देता है ।
    • आईटी इस clamp() कार्य यह सुनिश्चित करता है कि किसी दिए गए रंग का मूल्य सीमा (0-255) के भीतर है।
  • Solverवर्ग एक लक्ष्य रंग के लिए हल करने के लिए प्रयास करेंगे।
    • इसका css()कार्य सीएसएस फिल्टर स्ट्रिंग में दिए गए फिल्टर देता है।

लागू करना grayscale(),sepia() औरsaturate()

सीएसएस / एसवीजी फिल्टर का दिल हैं फिल्टर प्राइमिटिव हैं , जो एक छवि के निम्न-स्तरीय संशोधनों का प्रतिनिधित्व करते हैं।

फ़िल्टर grayscale(), sepia()और saturate()फ़िल्टर प्राइमरी द्वारा कार्यान्वित किए जाते हैं <feColorMatrix>, जो फ़िल्टर द्वारा निर्दिष्ट मैट्रिक्स (अक्सर गतिशील रूप से उत्पन्न), और रंग से निर्मित मैट्रिक्स के बीच मैट्रिक्स गुणन करता है । चित्र:

मैट्रिक्स गुणन

कुछ अनुकूलन हैं जो हम यहां कर सकते हैं:

  • रंग मैट्रिक्स का अंतिम तत्व है और हमेशा रहेगा 1 । इसकी गणना या भंडारण का कोई मतलब नहीं है।
  • अल्फा / ट्रांसपेरेंसी वैल्यू ( A) की गणना करने या संग्रहीत करने का कोई मतलब नहीं है , क्योंकि हम RGB के साथ काम कर रहे हैं, RGBA के साथ नहीं।
  • इसलिए, हम फ़िल्टर मैट्रिक्स को 5x5 से 3x5 तक और कलर मैट्रिक्स को 1x5 से 1x3 तक ट्रिम कर सकते हैं । इससे थोड़ा बहुत काम बच जाता है।
  • सभी <feColorMatrix>फ़िल्टर कॉलम 4 और 5 को शून्य के रूप में छोड़ देते हैं। इसलिए, हम फ़िल्टर मैट्रिक्स को 3x3 तक कम कर सकते हैं
  • चूंकि गुणा अपेक्षाकृत सरल है, इसके लिए जटिल गणित पुस्तकालयों में खींचने की कोई आवश्यकता नहीं है । हम स्वयं मैट्रिक्स गुणा एल्गोरिथ्म को लागू कर सकते हैं।

कार्यान्वयन:

function multiply(matrix) {
    let newR = this.clamp(this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]);
    let newG = this.clamp(this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]);
    let newB = this.clamp(this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]);
    this.r = newR; this.g = newG; this.b = newB;
}

(हम प्रत्येक पंक्ति गुणन के परिणामों को धारण करने के लिए अस्थायी चर का उपयोग करते हैं, क्योंकि हम बदलाव नहीं चाहते हैं this.r बाद की गणनाओं को प्रभावित करने वाले आदि को ।)

अब जब हम लागू कर दिया है <feColorMatrix>, हम लागू कर सकते हैं grayscale(), sepia()और saturate(), जो केवल यह आह्वान किसी दिए गए फिल्टर मैट्रिक्स के साथ:

function 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)
    ]);
}

function 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)
    ]);
}

function 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
    ]);
}

क्रियान्वयन hue-rotate()

hue-rotate()फिल्टर द्वारा कार्यान्वित किया जाता <feColorMatrix type="hueRotate" />

फ़िल्टर मैट्रिक्स की गणना नीचे दिखाए अनुसार की गई है:

उदाहरण के लिए, तत्व एक 00 तो जैसे गणना की जाएगी:

कुछ नोट:

  • रोटेशन के कोण को डिग्री में दिया गया है। यह रेडियंस में परिवर्तित किया जाना चाहिए करने के लिए पारित कर दिया इससे पहले कि Math.sin()या Math.cos()
  • Math.sin(angle)और Math.cos(angle)एक बार संकलित किया जाना चाहिए।

कार्यान्वयन:

function hueRotate(angle = 0) {
    angle = angle / 180 * Math.PI;
    let sin = Math.sin(angle);
    let 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.140, 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
    ]);
}

लागू करने brightness()औरcontrast()

brightness()और contrast()फिल्टर द्वारा कार्यान्वित किया जाता <feComponentTransfer>के साथ <feFuncX type="linear" />

प्रत्येक <feFuncX type="linear" />तत्व एक ढलान और अवरोधन विशेषता को स्वीकार करता है । इसके बाद एक सरल सूत्र के माध्यम से प्रत्येक नए रंग मान की गणना की जाती है:

value = slope * value + intercept

इसे लागू करना आसान है:

function linear(slope = 1, intercept = 0) {
    this.r = this.clamp(this.r * slope + intercept * 255);
    this.g = this.clamp(this.g * slope + intercept * 255);
    this.b = this.clamp(this.b * slope + intercept * 255);
}

एक बार यह लागू हो जाए, brightness()और contrast()इसे लागू किया जा सके:

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

क्रियान्वयन invert()

invert()फिल्टर द्वारा कार्यान्वित किया जाता <feComponentTransfer>के साथ <feFuncX type="table" />

युक्ति बताती है:

निम्नलिखित में, C प्रारंभिक घटक है और C ' रीमैप्ड घटक है; बंद अंतराल में दोनों [0,1]।

"तालिका" के लिए, समारोह विशेषता में दिए गए मूल्यों के बीच रैखिक प्रक्षेप द्वारा परिभाषित किया गया है tableValues । तालिका में n समान रूप से प्रक्षेप क्षेत्रों के लिए प्रारंभ और अंत मूल्यों को निर्दिष्ट करते हुए तालिका में n + 1 मान (जैसे, v 0 से v n ) है । प्रक्षेप निम्नलिखित सूत्र का उपयोग करते हैं:

एक मूल्य के लिए सी खोज कश्मीर ऐसी है कि:

k / n / C <(k + 1) / n

परिणाम C ' द्वारा दिया गया है:

C '= v k + (C - k / n) * n * (v k + 1 - v k )

इस सूत्र का स्पष्टीकरण:

  • invert()[- मूल्य मूल्य, 1]: फिल्टर इस तालिका को परिभाषित करता है। यह टेबलवैल्यू या वी है
  • सूत्र n को परिभाषित करता है , जैसे कि n + 1 तालिका की लंबाई है। चूंकि तालिका की लंबाई 2, n = 1 है।
  • सूत्र को परिभाषित करता है कश्मीर , साथ कश्मीर और कश्मीर + 1 तालिका के अनुक्रमित किया जा रहा है। चूंकि तालिका में 2 तत्व हैं, k = 0।

इस प्रकार, हम सूत्र को सरल बना सकते हैं:

C '= v 0 + C * (v 1 - v 0 )

तालिका के मूल्यों को सम्मिलित करते हुए, हम साथ रह गए हैं:

C '= मान + C * (1 - मान - मान)

एक और सरलीकरण:

C '= मान + C * (1 - 2 * मान)

सी ने सी और सी ' को परिभाषित किया आरजीबी मान, सीमा 0-1 के भीतर (जैसा कि 0-255 के विपरीत)। परिणामस्वरूप, हमें गणना करने से पहले मूल्यों को मापना चाहिए, और बाद में उन्हें वापस स्केल करना चाहिए।

इस प्रकार हम अपने कार्यान्वयन पर पहुँचते हैं:

function invert(value = 1) {
    this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255);
    this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255);
    this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255);
}

इंटरल्यूड: @ डेव्स ब्रूट-फोर्स एल्गोरिथम

@ डेव का कोड 176,660 फ़िल्टर संयोजन बनाता है , जिसमें शामिल हैं:

  • 1 1 invert() फ़िल्टर (0%, 10%, 20%, ..., 100%)
  • 1 1 sepia() फ़िल्टर (0%, 10%, 20%, ..., 100%)
  • 20 saturate() फ़िल्टर (5%, 10%, 15%, ..., 100%)
  • 73 hue-rotate()फ़िल्टर (0deg, 5deg, 10deg, ..., 360deg)

यह निम्नलिखित क्रम में फिल्टर की गणना करता है:

filter: invert(a%) sepia(b%) saturate(c%) hue-rotatedeg);

यह तब सभी गणना रंगों के माध्यम से पुनरावृत्त करता है। एक बार यह सहन करने के भीतर उत्पन्न रंग पा लेता है (सभी RGB मान लक्ष्य रंग से 5 इकाइयों के भीतर हैं) बंद हो जाता है।

हालांकि, यह धीमा और अक्षम है। इस प्रकार, मैं अपना जवाब प्रस्तुत करता हूं।

SPSA को लागू करना

सबसे पहले, हमें एक नुकसान फ़ंक्शन को परिभाषित करना चाहिए , जो फ़िल्टर संयोजन द्वारा उत्पादित रंग और लक्ष्य रंग के बीच का अंतर लौटाता है। यदि फ़िल्टर सही हैं, तो नुकसान फ़ंक्शन 0 पर लौटना चाहिए।

हम दो मीट्रिक के योग के रूप में रंग अंतर को मापेंगे:

  • RGB अंतर, क्योंकि लक्ष्य निकटतम RGB मान का उत्पादन करना है।
  • एचएसएल अंतर, क्योंकि कई एचएसएल मान फ़िल्टर के अनुरूप हैं (जैसे कि लगभग मोटे तौर पर सहसंबद्ध hue-rotate(), संतृप्ति सहसंबंध के साथ saturate(), आदि) यह एल्गोरिथ्म को निर्देशित करता है।

हानि फ़ंक्शन एक तर्क लेगा - फ़िल्टर प्रतिशत की एक सरणी।

हम निम्नलिखित फ़िल्टर क्रम का उपयोग करेंगे:

filter: invert(a%) sepia(b%) saturate(c%) hue-rotatedeg) brightness(e%) contrast(f%);

कार्यान्वयन:

function loss(filters) {
    let color = new Color(0, 0, 0);
    color.invert(filters[0] / 100);
    color.sepia(filters[1] / 100);
    color.saturate(filters[2] / 100);
    color.hueRotate(filters[3] * 3.6);
    color.brightness(filters[4] / 100);
    color.contrast(filters[5] / 100);

    let colorHSL = color.hsl();
    return Math.abs(color.r - this.target.r)
        + Math.abs(color.g - this.target.g)
        + Math.abs(color.b - this.target.b)
        + Math.abs(colorHSL.h - this.targetHSL.h)
        + Math.abs(colorHSL.s - this.targetHSL.s)
        + Math.abs(colorHSL.l - this.targetHSL.l);
}

हम नुकसान फ़ंक्शन को कम करने की कोशिश करेंगे, जैसे:

loss([a, b, c, d, e, f]) = 0

SPSA एल्गोरिथ्म ( वेबसाइट , अधिक जानकारी , कागज , कार्यान्वयन कागज , संदर्भ कोड ) इस पर बहुत अच्छा है। इसे स्थानीय मिनिमा, शोर / नॉनलाइनर / मल्टीवीरेट लॉस फ़ंक्शंस आदि के साथ जटिल प्रणालियों को अनुकूलित करने के लिए डिज़ाइन किया गया था। इसका उपयोग शतरंज इंजनों को ट्यून करने के लिए किया गया है । और कई अन्य एल्गोरिदम के विपरीत, यह वर्णन करने वाले कागजात वास्तव में बोधगम्य हैं (यद्यपि बड़े प्रयास के साथ)।

कार्यान्वयन:

function spsa(A, a, c, values, iters) {
    const alpha = 1;
    const gamma = 0.16666666666666666;

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

    for(let k = 0; k < iters; k++) {
        let ck = c / Math.pow(k + 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];
        }

        let lossDiff = this.loss(highArgs) - this.loss(lowArgs);
        for(let i = 0; i < 6; i++) {
            let g = lossDiff / (2 * ck) * deltas[i];
            let ak = a[i] / Math.pow(A + k + 1, alpha);
            values[i] = fix(values[i] - ak * g, i);
        }

        let loss = this.loss(values);
        if(loss < bestLoss) { best = values.slice(0); bestLoss = loss; }
    } return { values: best, loss: bestLoss };

    function fix(value, idx) {
        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 = value % max; }
            else if(value < 0) { value = max + value % max; }
        } else if(value < 0) { value = 0; }
        else if(value > max) { value = max; }
        return value;
    }
}

मैंने एसपीएएसए में कुछ संशोधन / अनुकूलन किए:

  • अंतिम के बजाय, उत्पादित सर्वोत्तम परिणाम का उपयोग करना।
  • सभी सरणियों पुनर्प्रयोग ( deltas, highArgs, lowArgs), बजाय उन्हें एक यात्रा के साथ पुनः बनाने की।
  • के लिए मूल्यों की एक सरणी का उपयोग करते हुए एक , एक भी मान के बजाय। ऐसा इसलिए है क्योंकि सभी फ़िल्टर अलग-अलग हैं, और इस तरह उन्हें अलग-अलग गति से आगे बढ़ना / जुटना चाहिए।
  • fixप्रत्येक पुनरावृत्ति के बाद एक समारोह चल रहा है । यह सभी मानों को 0% से 100% के बीच रखता है, saturate(जहां अधिकतम 7500% है) को छोड़कर , brightnessऔर contrast(जहां अधिकतम 200% है), और hueRotate(जहां मानों को क्लैंप के बजाय चारों ओर लपेटा जाता है)।

मैं दो चरणों की प्रक्रिया में SPSA का उपयोग करता हूं:

  1. "विस्तृत" चरण, जो खोज स्थान को "एक्सप्लोर" करने का प्रयास करता है। यदि परिणाम संतोषजनक नहीं हैं तो यह SPSA की सीमित वापसी कर देगा।
  2. "संकीर्ण" चरण, जो विस्तृत चरण से सबसे अच्छा परिणाम लेता है और इसे "परिष्कृत" करने का प्रयास करता है। यह और ए के लिए गतिशील मूल्यों का उपयोग करता है

कार्यान्वयन:

function solve() {
    let result = this.solveNarrow(this.solveWide());
    return {
        values: result.values,
        loss: result.loss,
        filter: this.css(result.values)
    };
}

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

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

function solveNarrow(wide) {
    const A = wide.loss;
    const c = 2;
    const A1 = A + 1;
    const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1];
    return this.spsa(A, a, c, wide.values, 500);
}

ट्यूनिंग SPSA

चेतावनी: SPSA कोड के साथ गड़बड़ न करें, विशेष रूप से इसके स्थिरांक के साथ, जब तक कि आप सुनिश्चित नहीं हैं कि आप जानते हैं कि आप क्या कर रहे हैं।

महत्वपूर्ण स्थिरांक हैं एक , एक , , प्रारंभिक मान, पुन: प्रयास करें थ्रेसहोल्ड, के मूल्यों maxमें fix(), और प्रत्येक चरण के पुनरावृत्तियों की संख्या। इन सभी मूल्यों को अच्छे परिणाम देने के लिए सावधानीपूर्वक तैयार किया गया था, और उनके साथ बेतरतीब ढंग से पेंच करना निश्चित रूप से एल्गोरिथ्म की उपयोगिता को कम कर देगा।

यदि आप इसे बदलने पर जोर देते हैं, तो आपको "अनुकूलन" करने से पहले मापना होगा।

सबसे पहले, इस पैच को लागू करें

फिर कोड को Node.js. में चलाएं काफी समय के बाद, परिणाम कुछ इस तरह होना चाहिए:

Average loss: 3.4768521401985275
Average time: 11.4915ms

अब स्थिरांक को अपने दिल की सामग्री पर ट्यून करें।

कुछ सुझाव:

  • औसत नुकसान लगभग 4 होना चाहिए। यदि यह 4 से अधिक है, तो यह ऐसे परिणाम पैदा कर रहा है जो बहुत दूर हैं, और आपको सटीकता के लिए ट्यून करना चाहिए। यदि यह 4 से कम है, तो यह समय बर्बाद कर रहा है, और आपको पुनरावृत्तियों की संख्या को कम करना चाहिए।
  • आप को बढ़ाते हैं /, पुनरावृत्तियों की संख्या में कमी को समायोजित एक उचित रूप से।
  • आप को बढ़ाते हैं / कमी एक , समायोजित एक उचित रूप से।
  • --debugयदि आप प्रत्येक पुनरावृत्ति का परिणाम देखना चाहते हैं तो ध्वज का उपयोग करें ।

टी एल; डॉ


3
विकास प्रक्रिया का बहुत अच्छा सारांश! क्या आप मेरे विचार पढ़ रहे हैं ?!
डेव

1
@ वास्तव में, मैं इस पर स्वतंत्र रूप से काम कर रहा था, लेकिन आपने मुझे इसके लिए हराया।
MultiplyByZer0


3
यह पूरी तरह से पागल विधि है। आप सीधे SVG फ़िल्टर (feColorMatrix में पांचवां कॉलम) का उपयोग करके एक रंग सेट कर सकते हैं और आप CSS से फ़िल्टर को संदर्भित कर सकते हैं - आप उस पद्धति का उपयोग क्यों नहीं करेंगे?
माइकल मुल्नी

2
@MichaelMullany खैर, यह मेरे लिए शर्मनाक है, यह देखते हुए कि मैंने इस पर कब तक काम किया। मैं अपने विधि के बारे में सोच नहीं था, लेकिन अब मैं समझता हूँ - किसी भी मनमाने ढंग से रंग करने के लिए एक तत्व पुन: रंग करने के लिए, तो आप सिर्फ गतिशील के साथ एक एसवीजी उत्पन्न एक <filter>एक युक्त <feColorMatrix>उचित मूल्यों के साथ (अंतिम स्तंभ है, जो लक्ष्य आरजीबी शामिल छोड़कर सभी शून्यों मान, 0 और 1), एसवीजी को डोम में डालें, और सीएसएस से फ़िल्टर का संदर्भ लें। कृपया अपने समाधान को एक उत्तर के रूप में (एक डेमो के साथ) लिखें, और मैं इसे बढ़ा दूंगा।
MultiplyByZer0

55

यह खरगोश छेद नीचे एक यात्रा काफी था, लेकिन यहाँ यह है!

var tolerance = 1;
var invertRange = [0, 1];
var invertStep = 0.1;
var sepiaRange = [0, 1];
var sepiaStep = 0.1;
var saturateRange = [5, 100];
var saturateStep = 5;
var hueRotateRange = [0, 360];
var hueRotateStep = 5;
var possibleColors;
var color = document.getElementById('color');
var pixel = document.getElementById('pixel');
var filtersBox = document.getElementById('filters');
var button = document.getElementById('button');
button.addEventListener('click', function() { 			      
	getNewColor(color.value);
})

// matrices taken from https://www.w3.org/TR/filter-effects/#feColorMatrixElement
function sepiaMatrix(s) {
	return [
		(0.393 + 0.607 * (1 - s)), (0.769 - 0.769 * (1 - s)), (0.189 - 0.189 * (1 - s)),
		(0.349 - 0.349 * (1 - s)), (0.686 + 0.314 * (1 - s)), (0.168 - 0.168 * (1 - s)),
		(0.272 - 0.272 * (1 - s)), (0.534 - 0.534 * (1 - s)), (0.131 + 0.869 * (1 - s)),
	]
}

function saturateMatrix(s) {
	return [
		0.213+0.787*s, 0.715-0.715*s, 0.072-0.072*s,
		0.213-0.213*s, 0.715+0.285*s, 0.072-0.072*s,
		0.213-0.213*s, 0.715-0.715*s, 0.072+0.928*s,
	]
}

function hueRotateMatrix(d) {
	var cos = Math.cos(d * Math.PI / 180);
	var sin = Math.sin(d * Math.PI / 180);
	var a00 = 0.213 + cos*0.787 - sin*0.213;
	var a01 = 0.715 - cos*0.715 - sin*0.715;
	var a02 = 0.072 - cos*0.072 + sin*0.928;

	var a10 = 0.213 - cos*0.213 + sin*0.143;
	var a11 = 0.715 + cos*0.285 + sin*0.140;
	var a12 = 0.072 - cos*0.072 - sin*0.283;

	var a20 = 0.213 - cos*0.213 - sin*0.787;
	var a21 = 0.715 - cos*0.715 + sin*0.715;
	var a22 = 0.072 + cos*0.928 + sin*0.072;

	return [
		a00, a01, a02,
		a10, a11, a12,
		a20, a21, a22,
	]
}

function clamp(value) {
	return value > 255 ? 255 : value < 0 ? 0 : value;
}

function filter(m, c) {
	return [
		clamp(m[0]*c[0] + m[1]*c[1] + m[2]*c[2]),
		clamp(m[3]*c[0] + m[4]*c[1] + m[5]*c[2]),
		clamp(m[6]*c[0] + m[7]*c[1] + m[8]*c[2]),
	]
}

function invertBlack(i) {
	return [
		i * 255,
		i * 255,
		i * 255,
	]
}

function generateColors() {
	let possibleColors = [];

	let invert = invertRange[0];
	for (invert; invert <= invertRange[1]; invert+=invertStep) {
		let sepia = sepiaRange[0];
		for (sepia; sepia <= sepiaRange[1]; sepia+=sepiaStep) {
			let saturate = saturateRange[0];
			for (saturate; saturate <= saturateRange[1]; saturate+=saturateStep) {
				let hueRotate = hueRotateRange[0];
				for (hueRotate; hueRotate <= hueRotateRange[1]; hueRotate+=hueRotateStep) {
					let invertColor = invertBlack(invert);
					let sepiaColor = filter(sepiaMatrix(sepia), invertColor);
					let saturateColor = filter(saturateMatrix(saturate), sepiaColor);
					let hueRotateColor = filter(hueRotateMatrix(hueRotate), saturateColor);

					let colorObject = {
						filters: { invert, sepia, saturate, hueRotate },
						color: hueRotateColor
					}

					possibleColors.push(colorObject);
				}
			}
		}
	}

	return possibleColors;
}

function getFilters(targetColor, localTolerance) {
	possibleColors = possibleColors || generateColors();

	for (var i = 0; i < possibleColors.length; i++) {
		var color = possibleColors[i].color;
		if (
			Math.abs(color[0] - targetColor[0]) < localTolerance &&
			Math.abs(color[1] - targetColor[1]) < localTolerance &&
			Math.abs(color[2] - targetColor[2]) < localTolerance
		) {
			return filters = possibleColors[i].filters;
			break;
		}
	}

	localTolerance += tolerance;
	return getFilters(targetColor, localTolerance)
}

function getNewColor(color) {
	var targetColor = color.split(',');
	targetColor = [
	    parseInt(targetColor[0]), // [R]
	    parseInt(targetColor[1]), // [G]
	    parseInt(targetColor[2]), // [B]
    ]
    var filters = getFilters(targetColor, tolerance);
    var filtersCSS = 'filter: ' +
	    'invert('+Math.floor(filters.invert*100)+'%) '+
	    'sepia('+Math.floor(filters.sepia*100)+'%) ' +
	    'saturate('+Math.floor(filters.saturate*100)+'%) ' +
	    'hue-rotate('+Math.floor(filters.hueRotate)+'deg);';
    pixel.style = filtersCSS;
    filtersBox.innerText = filtersCSS
}

getNewColor(color.value);
#pixel {
  width: 50px;
  height: 50px;
  background: rgb(0,0,0);
}
<input type="text" id="color" placeholder="R,G,B" value="250,150,50" />
<button id="button">get filters</button>
<div id="pixel"></div>
<div id="filters"></div>

EDIT: यह समाधान उत्पादन उपयोग के लिए अभिप्रेत नहीं है और केवल एक दृष्टिकोण को दिखाता है जिसे प्राप्त करने के लिए ओपी पूछ रहा है। जैसा कि, यह रंग स्पेक्ट्रम के कुछ क्षेत्रों में कमजोर है। कदम पुनरावृत्तियों में अधिक बारीकियों द्वारा या @ MultiplyByZer0 के उत्तर में विस्तार से वर्णित कारणों के लिए अधिक फ़िल्टर फ़ंक्शंस लागू करके बेहतर परिणाम प्राप्त किए जा सकते हैं ।

EDIT2: ओपी एक नॉन ब्रूट फोर्स सॉल्यूशन की तलाश में है। उस मामले में यह बहुत आसान है, बस इस समीकरण को हल करें:

सीएसएस फ़िल्टर मैट्रिक्स समीकरण

कहाँ पे

a = hue-rotation
b = saturation
c = sepia
d = invert

अगर मैं अंदर डालता हूं 255,0,255, तो मेरा डिजिटल रंग मीटर #d619d9इसके बजाय परिणाम की रिपोर्ट करता है #ff00ff
सिगुज़ा

@ सिगुजा यह निश्चित रूप से सही नहीं है, छोरों के छोरों को छोरों में सीमाओं को समायोजित करके रंगीन किया जा सकता है।
डेव

3
यह समीकरण कुछ भी हो लेकिन "बहुत सरल"
MultiplyByZer0

मुझे लगता है कि उपरोक्त समीकरण भी गायब है clamp?
ग्लीबम

1
क्लैंप का वहां कोई स्थान नहीं है। और जो मुझे अपने कॉलेज के गणित से याद है, इन समीकरणों की गणना संख्यात्मक गणना उर्फ ​​"जानवर बल" द्वारा की जाती है इसलिए शुभकामनाएं!
डेव

28

ध्यान दें: ओपी ने मुझसे कहा कि मैं इसे हटाना रद्द कर दूंगा, लेकिन इनाम डेव के जवाब पर जाएगा।


मुझे पता है कि यह वह नहीं है जो प्रश्न के शरीर में पूछा गया था, और निश्चित रूप से नहीं कि हम सब क्या कर रहे थे, लेकिन एक सीएसएस फ़िल्टर है जो वास्तव में ऐसा करता है: drop-shadow()

कैविट्स:

  • छाया मौजूदा सामग्री के पीछे खींची गई है। इसका मतलब है कि हमें कुछ पूर्ण पोजिशनिंग ट्रिक्स बनाने होंगे।
  • सभी पिक्सल्स को एक जैसा माना जाएगा, लेकिन ओपी ने कहा [हमें नहीं होना चाहिए] "काले रंग के अलावा अन्य रंगों का क्या होता है, इसकी देखभाल करना।"
  • ब्राउज़र का समर्थन। (मैं इसके बारे में निश्चित नहीं हूं, केवल लैट एफएफ और क्रोम के तहत परीक्षण किया गया है)।

/* the container used to hide the original bg */

.icon {
  width: 60px;
  height: 60px;
  overflow: hidden;
}


/* the content */

.icon.green>span {
  -webkit-filter: drop-shadow(60px 0px green);
  filter: drop-shadow(60px 0px green);
}

.icon.red>span {
  -webkit-filter: drop-shadow(60px 0px red);
  filter: drop-shadow(60px 0px red);
}

.icon>span {
  -webkit-filter: drop-shadow(60px 0px black);
  filter: drop-shadow(60px 0px black);
  background-position: -100% 0;
  margin-left: -60px;
  display: block;
  width: 61px; /* +1px for chrome bug...*/
  height: 60px;
  background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgOTAgOTAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDkwIDkwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48Zz48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTYxLjUxMSwyNi4xNWMtMC43MTQtMS43MzgtMS43MjMtMy4yOTgtMy4wMjYtNC42NzkgICBjLTEuMzAzLTEuMzY2LTIuODA5LTIuNDUyLTQuNTE1LTMuMjU5Yy0xLjc1NC0wLjgyMi0zLjYwMS0xLjI4OC01LjU0LTEuMzk2Yy0wLjI4LTAuMDMxLTAuNTUyLTAuMDQ3LTAuODE0LTAuMDQ3ICAgYy0wLjAxOCwwLTAuMDMxLDAtMC4wNDcsMGMtMC4zMjcsMC4wMTYtMC41NzQsMC4wMjMtMC43NDUsMC4wMjNjLTEuOTcxLDAuMTA4LTMuODQxLDAuNTc0LTUuNjA5LDEuMzk3ICAgYy0xLjcwOCwwLjgwNy0zLjIxMiwxLjg5My00LjUxNywzLjI1OWMtMS4zMTgsMS4zODEtMi4zMjcsMi45NDgtMy4wMjYsNC43MDJ2LTAuMDIzYy0wLjc0NCwxLjgxNS0xLjExOCwzLjcxNi0xLjExOCw1LjcwMiAgIGMtMC4wMTUsMi4wNjQsMC41MzcsNC4xODIsMS42NTQsNi4zNTVjMC41NzQsMS4xMzMsMS4yOTUsMi4yNSwyLjE2NCwzLjM1MmMwLjQ4MiwwLjYwNSwxLjAwMiwxLjIxLDEuNTYsMS44MTYgICBjMC4wMzEsMC4wMTYsMC4wNTUsMC4wMzksMC4wNzEsMC4wN2MwLjUyNywwLjQ5NiwwLjg5MiwwLjk3OCwxLjA5MywxLjQ0M2MwLjEwOCwwLjIzMywwLjE3OSwwLjUyLDAuMjEsMC44NjIgICBjMC4wNDYsMC4zNzEsMC4wNjksMC44MjIsMC4wNjksMS4zNXYxLjA0OGMwLDAuNjIsMC4xMTcsMS4yMTgsMC4zNDksMS43OTJjMC4yMzQsMC41NDMsMC41NiwxLjAyNCwwLjk3OCwxLjQ0M2gwLjAyNSAgIGMwLjQxOCwwLjQxOSwwLjg5MiwwLjc0NSwxLjQyLDAuOTc3aDAuMDIzYzAuNTU4LDAuMjQ5LDEuMTQ4LDAuMzczLDEuNzY5LDAuMzczaDcuMjg3YzAuNjIsMCwxLjIwOS0wLjEyNCwxLjc2OS0wLjM3MyAgIGMwLjU0My0wLjIzMSwxLjAyMy0wLjU1OCwxLjQ0My0wLjk3N2MwLjQxOC0wLjQxOSwwLjc0My0wLjksMC45NzgtMS40NDNjMC4yNDgtMC41NzQsMC4zNzEtMS4xNzIsMC4zNzEtMS43OTJ2LTEuMDQ4ICAgYzAtMC41MjcsMC4wMjMtMC45NzksMC4wNzEtMS4zNWMwLjAyOS0wLjM0MiwwLjA5Mi0wLjYzNywwLjE4Ni0wLjg4NWMwLjEwOC0wLjIzMywwLjI2NC0wLjQ3MywwLjQ2Ni0wLjcyMnYtMC4wMjMgICBjMC4xODctMC4yMzMsMC40MDMtMC40NjYsMC42NTEtMC42OTljMC4wMTYtMC4wMTYsMC4wMzEtMC4wMywwLjA0Ny0wLjA0NmMwLjU3NC0wLjYwNSwxLjEwMy0xLjIxLDEuNTgzLTEuODE2ICAgYzAuODY4LTEuMTAyLDEuNTkxLTIuMjE5LDIuMTY1LTMuMzUyYzEuMTE3LTIuMTczLDEuNjY3LTQuMjkxLDEuNjUyLTYuMzU1QzYyLjYwNSwyOS44NTksNjIuMjQsMjcuOTY2LDYxLjUxMSwyNi4xNXogICAgTTgxLjc4NSw0My4xNDJjMCw2Ljg3NS0xLjc1MywxMy4wMi01LjI2MSwxOC40MzZjLTEuMzgxLDIuMTQxLTMuMDMyLDQuMTY3LTQuOTU4LDYuMDc1Yy02Ljc1LDYuNzk3LTE0LjkxMywxMC4xOTUtMjQuNDg2LDEwLjE5NSAgIGMtNi40NTcsMC0xMi4yOTItMS41NDQtMTcuNTA1LTQuNjMyYy0wLjI0OSwwLjI5NS0wLjU2LDAuNTI3LTAuOTMyLDAuNjk4bC0xNi4xMzEsNy42NThjLTAuNTEyLDAuMjMzLTEuMDQ3LDAuMzAzLTEuNjA2LDAuMjEgICBjLTAuNTU5LTAuMDk0LTEuMDQtMC4zNDItMS40NDMtMC43NDVjLTAuNDA0LTAuNDAzLTAuNjUyLTAuODg2LTAuNzQ2LTEuNDQzYy0wLjA5My0wLjU2LTAuMDIzLTEuMDk0LDAuMjEtMS42MDVsNy42NTgtMTYuMjcxICAgYzAuMTQtMC4zMTEsMC4zMzQtMC41NzQsMC41ODMtMC43OTJjLTMuMTk3LTUuMjYxLTQuNzk2LTExLjE4OC00Ljc5Ni0xNy43ODRjMC05LjYyMSwzLjM3Ni0xNy44MDcsMTAuMTI1LTI0LjU1OCAgIGMwLjUyOC0wLjUyNywxLjA3MS0xLjA0LDEuNjMtMS41MzZjMi4yMDQtMS45NTYsNC41MzktMy41Nyw3LjAwNi00Ljg0MkMzNS45NDUsOS42OTIsNDEuMjYsOC40MzYsNDcuMDgsOC40MzYgICBjOS41NzMsMCwxNy43MzYsMy4zODIsMjQuNDg2LDEwLjE0OGM2LjQyNiw2LjM3OCw5LjgyNCwxNC4wMjksMTAuMTk1LDIyLjk1MkM4MS43NzgsNDIuMDYzLDgxLjc4NSw0Mi41OTksODEuNzg1LDQzLjE0MnogICAgTTUxLjM4NiwyNS4yNjZjLTAuNzE0LTAuMzI2LTEuNDU5LTAuNTEzLTIuMjM1LTAuNTU5Yy0wLjQ4LTAuMDMxLTAuODc2LTAuMjI1LTEuMTg4LTAuNTgzYy0wLjMxMS0wLjM0LTAuNDU3LTAuNzUyLTAuNDQxLTEuMjMzICAgYzAuMDMxLTAuNDY2LDAuMjI1LTAuODU0LDAuNTgyLTEuMTY1YzAuMzU3LTAuMzEsMC43NjktMC40NTcsMS4yMzQtMC40NDFjMS4yMjYsMC4wNzcsMi4zOTcsMC4zOCwzLjUxNSwwLjkwNyAgIGMxLjA2OSwwLjQ5NywyLjAxOCwxLjE3OSwyLjg0LDIuMDQ5YzAuODA3LDAuODY5LDEuNDM1LDEuODU0LDEuODg0LDIuOTU2YzAuNDY2LDEuMTMzLDAuNjk5LDIuMzIsMC42OTksMy41NjIgICBjMCwwLjQ2NS0wLjE3MSwwLjg2OS0wLjUxMiwxLjIxYy0wLjMyNSwwLjMyNi0wLjcyMiwwLjQ4OS0xLjE4OCwwLjQ4OWMtMC40OCwwLTAuODg0LTAuMTYzLTEuMjEtMC40ODkgICBjLTAuMzQyLTAuMzQxLTAuNTEzLTAuNzQ2LTAuNTEzLTEuMjFjMC0wLjc5Mi0wLjE0Ni0xLjU1Mi0wLjQ0MS0yLjI4MWMtMC4yNzktMC42OTktMC42ODMtMS4zMjctMS4yMTEtMS44ODYgICBTNTIuMDY3LDI1LjU5MSw1MS4zODYsMjUuMjY2eiBNNTcuNzg3LDM1LjM2OGMwLDAuNTEyLTAuMTg4LDAuOTU0LTAuNTYsMS4zMjZjLTAuMzU2LDAuMzU3LTAuOCwwLjUzNi0xLjMyNiwwLjUzNiAgIGMtMC41MTIsMC0wLjk0Ni0wLjE3OS0xLjMwMy0wLjUzNmMtMC4zNzQtMC4zNzItMC41Ni0wLjgxNC0wLjU2LTEuMzI2YzAtMC41MTMsMC4xODYtMC45NTYsMC41Ni0xLjMyNyAgIGMwLjM1Ni0wLjM1NywwLjc5MS0wLjUzNiwxLjMwMy0wLjUzNmMwLjUyNiwwLDAuOTcsMC4xNzgsMS4zMjYsMC41MzZDNTcuNiwzNC40MTMsNTcuNzg3LDM0Ljg1NSw1Ny43ODcsMzUuMzY4eiBNNTEuODk3LDU0LjcxMSAgIEg0My40Yy0wLjcxMiwwLTEuMzE4LDAuMjU2LTEuODE1LDAuNzY5Yy0wLjUxMiwwLjQ5Ny0wLjc2OSwxLjA5NC0wLjc2OSwxLjc5MmMwLDAuNzE0LDAuMjQ5LDEuMzE5LDAuNzQ2LDEuODE1bDAuMDIzLDAuMDI0ICAgYzAuNDk3LDAuNDk2LDEuMTAzLDAuNzQ0LDEuODE1LDAuNzQ0aDguNDk3YzAuNzE1LDAsMS4zMTgtMC4yNDgsMS44MTUtMC43NDRjMC40OTctMC41MTMsMC43NDUtMS4xMjYsMC43NDUtMS44NCAgIGMwLTAuNjk4LTAuMjQ4LTEuMjk1LTAuNzQ1LTEuNzkydi0wLjAyM0M1My4yMDEsNTQuOTU5LDUyLjU5Niw1NC43MTEsNTEuODk3LDU0LjcxMXogTTQyLjcyNiw2Mi40MzhoLTAuMDIzICAgYy0wLjQ5NywwLjQ5Ny0wLjc0NSwxLjEwMy0wLjc0NSwxLjgxNnMwLjI1NywxLjMxOCwwLjc2OSwxLjgxNWMwLjQ5NywwLjQ5NywxLjEwMiwwLjc0NSwxLjgxNiwwLjc0NWg2LjEyMiAgIGMwLjY5NywwLDEuMjk1LTAuMjQ4LDEuNzkyLTAuNzQ1aDAuMDIyYzAuNDk3LTAuNDk3LDAuNzQ2LTEuMTAyLDAuNzQ2LTEuODE1cy0wLjI0OS0xLjMxOS0wLjc0Ni0xLjgxNiAgIGMtMC41MTItMC41MTItMS4xMTctMC43NjgtMS44MTQtMC43NjhoLTYuMTIyQzQzLjgyOCw2MS42NzEsNDMuMjIzLDYxLjkyNyw0Mi43MjYsNjIuNDM4eiIvPjwvZz48L3N2Zz4=);
}
<div class="icon">
  <span></span>
</div>
<div class="icon green">
  <span></span>
</div>
<div class="icon red">
  <span></span>
</div>


1
सुपर चालाक, कमाल! यह मेरे लिए काम करता है, इसकी सराहना करते हैं
जामिनरो

मेरा मानना ​​है कि यह एक बेहतर उपाय है क्योंकि यह हर बार रंग के साथ 100% सटीक होता है।
user835542

कोड के रूप में एक खाली पृष्ठ (W10 FF 69b) दिखाता है। आइकन के साथ कुछ भी गलत नहीं है, हालांकि (अलग एसवीजी की जाँच की गई)।
रेने वान डेर लेंडे

जोड़ने background-color: black;के लिए .icon>spanबनाता है एफएफ 69b के लिए यह काम। हालाँकि, आइकन नहीं दिखा।
रेने वैन डेर लेंडे

@ RenevanderLende FF70 पर अभी भी वहाँ काम करता है की कोशिश की। यदि यह आपके लिए काम नहीं करता है, तो यह आपके अंत में कुछ होना चाहिए।
काइदो

15

आप CSS से संदर्भित SVG फ़िल्टर का उपयोग करके यह सब बहुत सरल बना सकते हैं । केवल एक पुनरावर्ती करने के लिए आपको एक ही feColorMatrix की आवश्यकता है। यह एक पीले रंग को याद करता है। FeColorMatrix में पांचवां स्तंभ इकाई पैमाने पर RGB लक्ष्य मान रखता है। (पीले रंग के लिए - यह 1,1,0 है)

.icon {
  filter: url(#recolorme); 
}
<svg height="0px" width="0px">
<defs>
  #ffff00
  <filter id="recolorme" color-interpolation-filters="sRGB">
    <feColorMatrix type="matrix" values="0 0 0 0 1
                                         0 0 0 0 1
                                         0 0 0 0 0
                                         0 0 0 1 0"/>
  </filter>
</defs>
</svg>


<img class="icon" src="https://www.nouveauelevator.com/image/black-icon/android.png">


एक दिलचस्प समाधान लेकिन ऐसा लगता है कि यह सीएसएस के माध्यम से लक्ष्य रंग को नियंत्रित करने की अनुमति नहीं देता है।
ग्लीबम

आपको प्रत्येक रंग के लिए एक नया फ़िल्टर परिभाषित करना होगा जिसे आप लागू करना चाहते हैं। लेकिन यह पूरी तरह से सही है। ह्यू-रोटेट एक अनुमान है जो कुछ रंगों को क्लिप करता है - जिसका अर्थ है कि आप निश्चित रूप से इसका उपयोग करके कुछ रंगों को सटीक रूप से प्राप्त नहीं कर सकते हैं - जैसा कि ऊपर के उत्तर में है। हम वास्तव में क्या जरूरत है एक recolor () सीएसएस फिल्टर आशुलिपि है।
माइकल मुल्नी

MultiplyByZer0 का उत्तर HTML को संशोधित किए बिना बहुत अधिक सटीकता के साथ प्राप्त होने वाले फ़िल्टर की एक श्रृंखला की गणना करता है। hue-rotateब्राउज़रों में एक सच अच्छा होगा हाँ।
glebm

2
ऐसा लगता है कि यह केवल ब्लैक सोर्स छवियों के लिए सटीक आरजीबी रंग का उत्पादन करता है जब आप "कलर-इंटरपोलेशन-फिल्टर" = "sRGB" को feColorMatrix में जोड़ते हैं।
जॉन स्मिथ

एज 12-18 को छोड़ दिया जाता है क्योंकि वे urlफंक्शन caniuse.com/#search=svg%20filter
Volker E.

2

मैंने देखा कि एसवीजी फ़िल्टर के माध्यम से उपचार का उदाहरण अधूरा था, मैंने अपना लिखा था (जो पूरी तरह से काम करता है): (माइकल मुल्नी का जवाब देखें) तो यहाँ आप चाहते हैं कि कोई भी रंग पाने का तरीका है:

यहाँ एक दूसरा उपाय है, केवल SVG फ़िल्टर का उपयोग करके = = URL.createObjectURL में


1

महज प्रयोग करें

fill: #000000

fillसीएसएस में संपत्ति एक एसवीजी आकार का रंग भरने के लिए है। fillसंपत्ति किसी भी सीएसएस रंग मान स्वीकार कर सकते हैं।


3
यह सीवीजी छवि के लिए सीएसएस आंतरिक के साथ काम कर सकता है, लेकिन यह सीएसएस के रूप imgमें ब्राउज़र द्वारा एक तत्व के लिए बाहरी रूप से लागू नहीं होता है।
डेविड मोल्स

0

मैंने इस जवाब के साथ शुरुआत की एक svg फ़िल्टर का उपयोग करके और निम्नलिखित संशोधन किए:

डेटा url से SVG फ़िल्टर

यदि आप अपने मार्कअप में कहीं भी एसवीजी फिल्टर को परिभाषित नहीं करना चाहते हैं , तो आप इसके बजाय एक डेटा यूआरएल का उपयोग कर सकते हैं ( आर , जी , बी और को वांछित रंग के साथ बदल सकते हैं):

filter: url('data:image/svg+xml;utf8,\
  <svg xmlns="http://www.w3.org/2000/svg">\
    <filter id="recolor" 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>\
  #recolor');

ग्रेस्केल फॉलबैक

यदि उपरोक्त संस्करण काम नहीं करता है, तो आप एक ग्रेस्केल फ़ॉलबैक भी जोड़ सकते हैं।

saturateऔर brightnessकार्यों काला करने के लिए किसी भी रंग, (आप शामिल करना है कि अगर रंग पहले से ही काला है नहीं है) की बारी invertतो यह वांछित हल्कापन (साथ में रोशनी फैला एल ) और वैकल्पिक रूप से आप भी अस्पष्टता (निर्दिष्ट कर सकते हैं एक )।

filter: saturate(0%) brightness(0%) invert(L) opacity(A);

SCSS मिश्रण

यदि आप गतिशील रूप से रंग निर्दिष्ट करना चाहते हैं, तो आप निम्नलिखित SCSS मिश्रण का उपयोग कर सकते हैं:

@mixin recolor($color: #000, $opacity: 1) {
  $r: red($color) / 255;
  $g: green($color) / 255;
  $b: blue($color) / 255;
  $a: $opacity;

  // grayscale fallback if SVG from data url is not supported
  $lightness: lightness($color);
  filter: saturate(0%) brightness(0%) invert($lightness) opacity($opacity);

  // 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}');
}

उदाहरण उपयोग:

.icon-green {
  @include recolor(#00fa86, 0.8);
}

लाभ:

  • कोई जावास्क्रिप्ट नहीं
  • कोई अतिरिक्त HTML तत्व नहीं
  • यदि CSS फ़िल्टर समर्थित हैं, लेकिन SVG फ़िल्टर काम नहीं करता है, तो एक ग्रेस्केल फॉलबैक है
  • यदि आप मिश्रण का उपयोग करते हैं, तो उपयोग बहुत सीधा है (ऊपर उदाहरण देखें)।
  • सीपिया ट्रिक (शुद्ध सीएसएस में आरजीबीए घटकों और आप एससीएसएस में एचईएक्स रंगों का उपयोग भी कर सकते हैं) की तुलना में रंग अधिक पठनीय और आसान है।
  • के अजीब व्यवहार सेhue-rotate बचा जाता है

चेतावनियां:

  • सभी ब्राउज़र डेटा यूआरएल (विशेष रूप से आईडी हैश) से एसवीजी फिल्टर का समर्थन नहीं करते हैं , लेकिन यह वर्तमान फ़ायरफ़ॉक्स और क्रोमियम ब्राउज़र (और शायद अन्य) में काम करता है
  • यदि आप गतिशील रूप से रंग निर्दिष्ट करना चाहते हैं, तो आपको SCSS मिश्रण का उपयोग करना होगा।
  • शुद्ध सीएसएस संस्करण थोड़ा बदसूरत है, यदि आप कई अलग-अलग रंगों को चाहते हैं तो आपको कई बार एसवीजी शामिल करना होगा।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.