Fer*_*Fer 6 css twitter image colors
让我先澄清问题陈述。看看这条推文:
https://twitter.com/jungledragon/status/926894337761345538
接下来,单击推文中的图像本身。在出现的灯箱中,其下方的菜单栏采用基于图像本身实际像素的有意义的颜色。即使在这个压力测试中,考虑到所有亮像素,这也是一个困难的图像,它在选择整体颜色方面做得很好,1) 代表图像的内容 2) 足够暗/对比度足以在其上放置白色文本:
在我知道 Twitter 有这个系统之前,我同时实施了一个类似的系统。查看下面的预览:
截图中的例子是乐观的,因为有很多背景太亮的情况。即使在我的截图中看到的看似正面的例子中,大部分时间它也没有通过 AA 或 AAA 对比度检查。
我目前的做法:
我的公式是将 RGB 转换为 HSL,然后特别处理 S 和 L 值。给他们一个缺口,使用最小/最大值来设置阈值。我试过无数组合。
然而,这似乎是一场永无止境的斗争,因为颜色暗度和对比度受人类感知的影响。
因此,我对 Twitter 似乎如何解决这一问题感到好奇,尤其是两个方面:
我四处搜索,但找不到有关其实施的任何信息。有人知道他们这样做吗?或者其他经过验证的方法可以端到端地解决这个难题?
我看了一下 Twitter 的标记,看看能找到什么,在浏览器控制台中运行了一些代码后,Twitter 似乎对图像中像素的平坦分布进行了颜色平均值,并对每个 RGB 进行了缩放通道到 64 及以下的值。这提供了一种非常快速的方法来为浅色文本创建高对比度背景,同时仍然保留合理的颜色匹配。据我所知,Twitter 没有执行任何高级主题颜色检测,但我不能肯定地说。
这是我为了验证这个理论而制作的一个简单的演示。图像周围出现的顶部和左侧边框最初显示 Twitter 使用的颜色。运行代码片段后,底部和右边框将显示为计算出的颜色。IE 用户需要 9+。
function processImage(img)
{
var imageCanvas = new ImageCanvas(img);
var tally = new PixelTally();
for (var y = 0; y < imageCanvas.height; y += config.interval) {
for (var x = 0; x < imageCanvas.width; x += config.interval) {
tally.record(imageCanvas.getPixelColor(x, y));
}
}
var average = new ColorAverage(tally);
img.style.borderRightColor = average.toRGBStyleString();
img.style.borderBottomColor = average.toRGBStyleString();
}
function ImageCanvas(img)
{
var canvas = document.createElement('canvas');
this.context2d = canvas.getContext('2d');
this.width = canvas.width = img.naturalWidth;
this.height = canvas.height = img.naturalHeight;
this.context2d.drawImage(img, 0, 0, this.width, this.height);
this.getPixelColor = function (x, y) {
var pixel = this.context2d.getImageData(x, y, 1, 1).data;
return { red: pixel[0], green: pixel[1], blue: pixel[2] };
}
}
function PixelTally()
{
this.totalPixelCount = 0;
this.colorPixelCount = 0;
this.red = 0;
this.green = 0;
this.blue = 0;
this.luminosity = 0;
this.record = function (colors) {
this.luminosity += this.calculateLuminosity(colors);
this.totalPixelCount++;
if (this.isGreyscale(colors)) {
return;
}
this.red += colors.red;
this.green += colors.green;
this.blue += colors.blue;
this.colorPixelCount++;
};
this.getAverage = function (colorName) {
return this[colorName] / this.colorPixelCount;
};
this.getLuminosityAverage = function () {
return this.luminosity / this.totalPixelCount;
}
this.getNormalizingDenominator = function () {
return Math.max(this.red, this.green, this.blue) / this.colorPixelCount;
};
this.calculateLuminosity = function (colors) {
return (colors.red + colors.green + colors.blue) / 3;
};
this.isGreyscale = function (colors) {
return Math.abs(colors.red - colors.green) < config.greyscaleDistance
&& Math.abs(colors.red - colors.blue) < config.greyscaleDistance;
};
}
function ColorAverage(tally)
{
var lightness = config.lightness;
var normal = tally.getNormalizingDenominator();
var luminosityAverage = tally.getLuminosityAverage();
// We won't scale the channels up to 64 for darker images:
if (luminosityAverage < lightness) {
lightness = luminosityAverage;
}
this.red = (tally.getAverage('red') / normal) * lightness
this.green = (tally.getAverage('green') / normal) * lightness
this.blue = (tally.getAverage('blue') / normal) * lightness
this.toRGBStyleString = function () {
return 'rgb('
+ Math.round(this.red) + ','
+ Math.round(this.green) + ','
+ Math.round(this.blue) + ')';
};
}
function Configuration()
{
this.lightness = 64;
this.interval = 100;
this.greyscaleDistance = 15;
}
var config = new Configuration();
var indicator = document.getElementById('indicator');
document.addEventListener('DOMContentLoaded', function () {
document.forms[0].addEventListener('submit', function (event) {
event.preventDefault();
config.lightness = Number(this.elements['lightness'].value);
config.interval = Number(this.elements['interval'].value);
config.greyscaleDistance = Number(this.elements['greyscale'].value);
indicator.style.visibility = 'visible';
setTimeout(function () {
processImage(document.getElementById('image1'));
processImage(document.getElementById('image2'));
processImage(document.getElementById('image3'));
processImage(document.getElementById('image4'));
processImage(document.getElementById('image5'));
indicator.style.visibility = 'hidden';
}, 50);
});
});Run Code Online (Sandbox Code Playgroud)
label { display: block; }
img { border-width: 20px; border-style: solid; width: 200px; height: 200px; }
#image1 { border-color: rgb(64, 54, 47) white white rgb(64, 54, 47); }
#image2 { border-color: rgb(46, 64, 17) white white rgb(46, 64, 17); }
#image3 { border-color: rgb(64, 59, 46) white white rgb(64, 59, 46); }
#image4 { border-color: rgb(36, 38, 20) white white rgb(36, 38, 20); }
#image5 { border-color: rgb(45, 53, 64) white white rgb(45, 53, 64); }
#indicator { visibility: hidden; }Run Code Online (Sandbox Code Playgroud)
<form id="configuration_form">
<p>
<label>Lightness:
<input name="lightness" type="number" min="1" max="255" value="64">
</label>
<label>Pixel Sample Interval:
<input name="interval" type="number" min="1" max="255" value="100">
(Lower values are slower)
</label>
<label>Greyscale Distance:
<input name="greyscale" type="number" min="1" max="255" value="15">
</label>
<button type="submit">Run</button> (Wait for images to load first!)
</p>
<p id="indicator">Running...this may take a few moments.</p>
</form>
<p>
<img id="image1" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DNz9fNqWAAAtoGu.jpg:large">
<img id="image2" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DOdX8AGXUAAYYmq.jpg:large">
<img id="image3" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DOYp0HQX4AEWcnI.jpg:large">
<img id="image4" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DOQm1NzXkAEwxG7.jpg:large">
<img id="image5" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DN6gVnpXUAIxlxw.jpg:large">
</p>Run Code Online (Sandbox Code Playgroud)
在确定图像的主色时,代码会忽略白色、黑色和灰色像素,尽管降低了颜色的亮度,但仍为我们提供了更鲜艳的饱和度。大多数图像的计算颜色与 Twitter 的原始颜色非常接近。
我们可以通过改变计算平均颜色的图像部分来改进这个实验。上面的示例在整个图像上均匀地选择像素,但我们可以尝试仅使用图像边缘附近的像素,以便颜色混合得更加无缝,或者我们可以尝试平均图像中心的颜色值以突出显示主题。当我有更多时间时,我将扩展代码并更新此答案。