清明节黑白效果=>来聊聊色彩矩阵算法

昨天各大平台收到通知,需要首页以黑白效果来展示,高级浏览器有一个简单的属性,全局设置一下,就可以满足:

-webkit-filter: grayscale(100%);filter: grayscale(100%);

在低级浏览器可以借助第三方插件来实现,比如说grayscale.js这种。

这种插件的原理,简单来说就是递归DOM结构,判断节点的类型,如果是文本直接更改其颜色值,如果是图片,通过canvas来更改颜色值。

如何改变图片的颜色呢,就是今天咱们来聊的色彩矩阵。

其实就是把每个像素值跟多维矩阵乘一下就得到一个新的像素值(rgba)(底部有封装好的初始矩阵通道)

matrix!:Array = [];

RGBA = [ R, G, B, A ]

matrix =[ 1, 0, 0, 0, 0,
          0, 1, 0, 0, 0,
          0, 0, 1, 0, 0,
          0, 0, 0, 1, 0 ] //原始通道

新的颜色信息 = RGBA * matrix = [
                matrix[0]*R + matrix[1]*G + matrix[2]*B + matrix[3]*A + matrix[4] // R通道
                matrix[5]*R + matrix[6]*G + matrix[7]*B + matrix[8]*A + matrix[9] // G通道
                matrix[10]*R + matrix[11]*G + matrix[12]*B + matrix[13]*A + matrix[14] // B通道
                matrix[15]*R + matrix[16]*G + matrix[17]*B + matrix[18]*A + matrix[19] // A通道
             ]

matrix由 20 个项目组成的数组,适用于 4 x 5 颜色转换。

red通道的值:(1,0,0,0,0)表示,R通道的乘数是1(完全保留),别的道道的的乘数是0,(不加入别的通道的颜色),色彩偏移量off是0;

green通道的值:(0,  1,  0,  0,  0)表示,G通道的乘数是1(完全保留),别的道道的的乘数是0,(不加入别的通道的颜色),色彩偏移量off是0;

blue通道的值:(0,  0,  1,  0,  0)表示,B通道的乘数是1(完全保留),别的道道的的乘数是0,(不加入别的通道的颜色),色彩偏移量off是0;

alpha通道的值:(0,  0,  0,  1,  0)表示,A通道的乘数是1(完全保留),别的道道的的乘数是0,(不加入别的通道的颜色),色彩偏移量off是0;

4 x 5  4行5列我们已经知晓其4列(RGBA),至于第5列(简称N)是颜色亮度的调节:

1,0,0,0,N
0,1,0,0,N
0,0,1,0,N
0,0,0,1,0

N=>亮度取值范围-255到255,只需要设置一下RGB的色彩偏移就能调节其亮度

举例说明:如果想要一个值为 rgba(200, 100, 50, 1 ) 的颜色,只保留红色通道的色彩。我们只需要调整 matrix 并做如下运算:

[ 200, 100, 50, 1 ] * [ 1, 0, 0, 0, 0,
                        0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0,
                        0, 0, 0, 1, 0 ] = [
                            1*200 + 0*100 + 0*50 + 0*1 + 0,
                            0*200 + 0*100 + 0*50 + 0*1 +0,
                            0*200 + 0*100 + 0*50 + 0*1 +0,
                            0*200 + 0*100 + 0*50 + 1*1 +0,
                        ] = [ 200, 0, 0, 1 ]

颜色反转

[ 200, 100, 50, 1 ] * [ -1, 0, 0, 0, 255,
                        0, -1, 0, 0, 255,
                        0, 0, -1, 0, 255,
                        0, 0, 0, 1, 0 ] = [
                            -1*200 + 0*100 + 0*50 + 0*1 + 255,
                            0*200 + -1*100 + 0*50 + 0*1 +255
                            0*200 + 0*100 + -1*50 + 0*1 +255,
                            0*200 + 0*100 + 0*50 + 1*1 +0,
                        ] = [ 206, 206, 206, 1]

咱们昨天的黑白是如何实现的呢,颜色去色,同样是矩阵算法,但是通道值却不是跟上面一样的1:

matrix = [ 0.3086, 0.6094, 0.0820, 0, 0
            0.3086, 0.6094, 0.0820, 0, 0
            0.3086, 0.6094, 0.0820, 0, 0
            0 , 0 , 0, 1, 0 ]

只要把RGB三通道的色彩信息设置成一样;即:R=G=B,那么图像就变成了灰色,并且,为了保证图像亮度不变,同一个通道中的R+G+B=1,很多人会奇怪为啥是这三个小数,其实是把RGB做了一个比例均分:大概是3:6:1,即:0.3086, 0.6094, 0.0820

看到这里来了,应该就会明白更改图片颜色的关键是矩阵乘法,通过不同的多维矩阵原始值,来达到不同的效果,包括咱们看到的微信图片滤镜,以及社交平台ins的图片滤镜,也是通过多维矩阵算法来实现一个图片滤镜。

下面看看,我们可以利用这个玩意最终来做成什么效果(家中浩克出来当次模特吧):

以下是每个不同效果封装好的初始矩阵值(就好像上面说的,同一个通道的RGB相加必须等于1 => R+G+B=1)

desaturate: function() {
			return this.add([
						FILTER.LUMA[0], FILTER.LUMA[1], FILTER.LUMA[2], 0, 0, 
						FILTER.LUMA[0], FILTER.LUMA[1], FILTER.LUMA[2], 0, 0, 
						FILTER.LUMA[0], FILTER.LUMA[1], FILTER.LUMA[2], 0, 0, 
						0, 0, 0, 1, 0
					]);
		},
		//平均 返回灰色图片 是对红绿蓝进行加权平均数的计算
		average: function( r, g, b ) {
			if ( r === undefined ) r = 0.3333;
			if ( g === undefined ) g = 0.3333;
			if ( b === undefined ) b = 0.3334;
			
			return this.add([
					r, g, b, 0, 0, 
					r, g, b, 0, 0, 
					r, g, b, 0, 0, 
					0, 0, 0, 1, 0
				]);
		},
		
		//明度  对红绿蓝进行数值偏移计算
		brightness: function( r, g, b ) {
			if ( g === undefined )  g = r;
			if ( b === undefined )  b = r;
			return this.add([
					1, 0, 0, 0, r, 
					0, 1, 0, 0, g, 
					0, 0, 1, 0, b, 
					0, 0, 0, 1, 0
				]);
		},
		
		
		
		//着色
		colorize: function( rgb, alpha ) {
			var r, g, b, inv_alpha;
			if ( alpha === undefined ) alpha = 1;
			//var alpha = alpha ? 0 : 1;
			r = (((rgb >> 16) & 255) * 0.0039215686274509803921568627451);  // / 255
			g = (((rgb >> 8) & 255) * 0.0039215686274509803921568627451);  // / 255
			b = ((rgb & 255) * 0.0039215686274509803921568627451);  // / 255
			inv_alpha = 1 - alpha;
			
			//console.log((rgb >> 8).toString(2))
			//console.log((255).toString(2))
			//console.log(((rgb >> 8) & 255).toString(2))
			
			//console.log((inv_alpha + ((alpha * r) * FILTER.LUMA[0])), ((alpha * r) * FILTER.LUMA[1]), ((alpha * r) * FILTER.LUMA[2]), 0, 0)
			//console.log(((alpha * g) * FILTER.LUMA[0]), (inv_alpha + ((alpha * g) * FILTER.LUMA[1])), ((alpha * g) * FILTER.LUMA[2]), 0, 0)
			//console.log(((alpha * b) * FILTER.LUMA[0]), ((alpha * b) * FILTER.LUMA[1]), (inv_alpha + ((alpha * b) * FILTER.LUMA[2])), 0, 0)
	
			return this.add([
						(inv_alpha + ((alpha * r) * FILTER.LUMA[0])), ((alpha * r) * FILTER.LUMA[1]), ((alpha * r) * FILTER.LUMA[2]), 0, 0, 
						((alpha * g) * FILTER.LUMA[0]), (inv_alpha + ((alpha * g) * FILTER.LUMA[1])), ((alpha * g) * FILTER.LUMA[2]), 0, 0, 
						((alpha * b) * FILTER.LUMA[0]), ((alpha * b) * FILTER.LUMA[1]), (inv_alpha + ((alpha * b) * FILTER.LUMA[2])), 0, 0, 
							0, 0, 0, 1, 0
						]);
		},
		
		//反向
		invert: function( ) {
			return this.add([
					-1, 0,  0, 0, 255,
					0, -1,  0, 0, 255,
					0,  0, -1, 0, 255,
					0,  0,  0, 1,   0
				]);
		},
		
		invertRed: function( ) {
			return this.add([
					-1, 0,  0, 0, 255,
					0,  1,  0, 0, 255,
					0,  0,  1, 0, 255,
					0,  0,  0, 1,   0
				]);
		},
		
		invertGreen: function( ) {
			return this.add([
					1,  0,  0, 0, 255,
					0,  -1, 0, 0, 255,
					0,  0,  1, 0, 255,
					0,  0,  0, 1,   0
				]);
		},
		
		invertBlue: function( ) {
			return this.add([
					1,  0,  0, 0, 255,
					0,  1,  0, 0, 255,
					0,  0, -1, 0, 255,
					0,  0,  0, 1,   0
				]);
		},
		
		//alpha 反向
		invertAlpha: function( ) {
			return this.add([
					1,  0,  0, 0, 0,
					0,  1,  0, 0, 0,
					0,  0,  1, 0, 0,
					0,  0,  0, -1, 255
				]);
		},
		
		//饱和度
		saturate: function( s ) {
			
			var sInv, irlum, iglum, iblum;
			sInv = (1 - s);
			irlum = (sInv * FILTER.LUMA[0]);
			iglum = (sInv * FILTER.LUMA[1]);
			iblum = (sInv * FILTER.LUMA[2]);
			
			return this.add([
					(irlum + s), iglum, iblum, 0, 0, 
					irlum, (iglum + s), iblum, 0, 0, 
					irlum, iglum, (iblum + s), 0, 0, 
					0, 0, 0, 1, 0
				]);
		},
		
		//对比度
		contrast: function( r, g, b ) {
			if ( g === undefined )  g = r;
			if ( b === undefined )  b = r;
			r += 1.0; g += 1.0; b += 1.0;
			return this.add([
					r, 0, 0, 0, (128 * (1 - r)), 
					0, g, 0, 0, (128 * (1 - g)), 
					0, 0, b, 0, (128 * (1 - b)), 
					0, 0, 0, 1, 0
				]);
		},
		//快速校正对比度
		quickContrastCorrection: function( contrast ) {
			if ( contrast === undefined ) contrast = 1.2;
			return this.add([
				contrast, 0, 0, 0, 0, 
				0, contrast, 0, 0, 0, 
				0, 0, contrast, 0, 0, 
				0, 0, 0, 1, 0
				]);
		},
		
		//色相调整 色环 0-360
		adjustHue: function( degrees ) {
			degrees *= FILTER.CONSTANTS.toRad;
			var cos = Math.cos(degrees), sin = Math.sin(degrees);
			
			return this.add([
					((FILTER.LUMA[0] + (cos * (1 - FILTER.LUMA[0]))) + (sin * -(FILTER.LUMA[0]))), ((FILTER.LUMA[1] + (cos * -(FILTER.LUMA[1]))) + (sin * -(FILTER.LUMA[1]))), ((FILTER.LUMA[2] + (cos * -(FILTER.LUMA[2]))) + (sin * (1 - FILTER.LUMA[2]))), 0, 0, 
					((FILTER.LUMA[0] + (cos * -(FILTER.LUMA[0]))) + (sin * 0.143)), ((FILTER.LUMA[1] + (cos * (1 - FILTER.LUMA[1]))) + (sin * 0.14)), ((FILTER.LUMA[2] + (cos * -(FILTER.LUMA[2]))) + (sin * -0.283)), 0, 0, 
					((FILTER.LUMA[0] + (cos * -(FILTER.LUMA[0]))) + (sin * -((1 - FILTER.LUMA[0])))), ((FILTER.LUMA[1] + (cos * -(FILTER.LUMA[1]))) + (sin * FILTER.LUMA[1])), ((FILTER.LUMA[2] + (cos * (1 - FILTER.LUMA[2]))) + (sin * FILTER.LUMA[2])), 0, 0, 
					0, 0, 0, 1, 0
				]);
		},
		
		
		
		//sepia 偏色  褐色调旧照片。
		sepia: function( amount ) {
			if ( amount === undefined ) amount = 0.5;
			if ( amount > 1 ) amount = 1;
			else if ( amount < 0 ) amount = 0;
			return this.add([
				1.0 - (0.607 * amount), 0.769 * amount, 0.189 * amount, 0, 0, 
				0.349 * amount, 1.0 - (0.314 * amount), 0.168 * amount, 0, 0, 
				0.272 * amount, 0.534 * amount, 1.0 - (0.869 * amount), 0, 0, 
				0, 0, 0, 1, 0
			]);
		},
		
		//sepia2 偏色  红褐色调旧照片。
		sepia2: function( amount ) {
			if ( amount === undefined ) amount = 10;
			if ( amount > 100 ) amount = 100;
			amount *= 2.55;
			return this.add([
				FILTER.LUMA[0], FILTER.LUMA[1], FILTER.LUMA[2], 0, 40, 
				FILTER.LUMA[0], FILTER.LUMA[1], FILTER.LUMA[2], 0, 20, 
				FILTER.LUMA[0], FILTER.LUMA[1], FILTER.LUMA[2], 0, -amount, 
				0, 0, 0, 1, 0
			]);
		},
		//灰白阈值
		threshold: function( threshold, factor ) {
			if ( factor === undefined )  factor = 256;
			
			return this.add([
					(FILTER.LUMA[0] * factor), (FILTER.LUMA[1] * factor), (FILTER.LUMA[2] * factor), 0, (-(factor-1) * threshold), 
					(FILTER.LUMA[0] * factor), (FILTER.LUMA[1] * factor), (FILTER.LUMA[2] * factor), 0, (-(factor-1) * threshold), 
					(FILTER.LUMA[0] * factor), (FILTER.LUMA[1] * factor), (FILTER.LUMA[2] * factor), 0, (-(factor-1) * threshold), 
					0, 0, 0, 1, 0
				]);
		},
		
		
		//阈值 保留彩色
		threshold_rgb: function( threshold, factor ) {
			if ( factor === undefined )  factor = 256;
			
			return this.add([
					factor, 0, 0, 0, (-(factor-1) * threshold), 
					0, factor, 0, 0, (-(factor-1) * threshold), 
					0,  0, factor, 0, (-(factor-1) * threshold), 
					0, 0, 0, 1, 0
				]);
		},
		
		/*threshold_alpha: function( threshold, factor ) {
			if ( threshold === undefined )  threshold = 0.5;
			if ( factor === undefined ) factor = 256;
			
			return this.add([
					1, 0, 0, 0, 0, 
					0, 1, 0, 0, 0, 
					0, 0, 1, 0, 0, 
					0, 0, 0, factor, (-factor * threshold)
				]);
		},*/

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用循环遍历的方式获取每一年的十二节气阳历日期。具体实现如下: ``` import React, { useState } from 'react' import calendar from 'js-calendar-converter' const j = ['立春', '惊蛰', '清明', '立夏', '芒种', '小暑', '立秋', '白露', '寒露', '立冬', '大雪', '小寒'] const News = () => { const [solarDates, setSolarDates] = useState([]) const getSolarDates = (year) => { const solarDatesOfYear = [] for (let i = 0; i < j.length; i++) { const solarDate = calendar.lunar2solar(year, i * 2 + 3, 1) // 通过农历日期计算阳历日期 solarDatesOfYear.push({ term: j[i], year: solarDate.cYear, month: solarDate.cMonth, day: solarDate.cDay, date: `${solarDate.cYear}-${solarDate.cMonth}-${solarDate.cDay}` }) } return solarDatesOfYear } const handleConfirm = (val) => { const year = val.getFullYear() const solarDatesOfYear = getSolarDates(year) setSolarDates(solarDatesOfYear) } return ( <div> <DatePicker onConfirm={handleConfirm} /> <ul> {solarDates.map((solarDate, index) => ( <li key={index}>{solarDate.term}: {solarDate.date}</li> ))} </ul> </div> ) } ``` 首先,在组件中添加一个状态变量`solarDates`,用于存储每一年的十二节气阳历日期。 在`getSolarDates`函数中,使用循环遍历的方式获取每一年的十二节气阳历日期。具体实现为,通过计算每个节气对应的农历日期,然后将其转换为阳历日期。最后将阳历日期存储到`solarDatesOfYear`数组中。 在`handleConfirm`函数中,获取选中的年份,然后调用`getSolarDates`函数获取该年份的十二节气阳历日期,并将其存储到`solarDates`状态变量中。 在组件中渲染`solarDates`数组中的阳历日期。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值