当前位置: 首页 > Web前端 > JavaScript

提取图片颜色-前端解决方案

时间:2023-03-26 20:19:09 JavaScript

前言去年我有一个想法。通过提取画面的主题色,与画面形成身临其境的视觉效果,营造和谐一致的感觉。我用了一个星期的时间,通过客户端和服务端两种方式实现了这个功能。这两种方法都有自己的优点和缺点。通常,主题颜色的提取是在服务器上完成的。客户端将需要处理的图片以链接或id的形式提供给服务器,服务器通过运行相应的算法提取主题色,然后返回相应的结果。这样可以满足大部分展示场景,但是对于需要根据用户“定制”和“生成”的图片,这个方法有上传图片---->服务器计算---->返回结果时间的方法,等待时间可能会更长。在客户端实现中,手机浏览器的兼容性是一件很痛苦的事情。上面有点麻烦。简单来说,在大多数情况下,建议选择服务器来实现。在用户实时定制和图像生成的情况下,可以选择颜色需求的提取和移动端来实现。这篇文章给大家分享一下客户端的实现方案:目前提取图像颜色最常用的主题色提取算法有:最小差分法、中值分割法、八叉树算法、聚类法、颜色建模法、等等,这里我选择了中值切割的方法来实现。mid-bitsegmentation的思想通常是图像处理中降低图像位深度的一种算法,可以用来将高位图像转换为低位图像,比如转换24位的图像图像到8位图像。我们也可以用它来提取图片的主题色。其原理是将图像的每个像素点的颜色看成是三维空间中以R、G、B为坐标轴的一个点。由于三种颜色的取值范围是0~255,所以图像中的颜色分布在这个颜色立方体中,如下图所示:从初始的整幅图像开始为一个长方体,然后将其最长的边进行划分RGB从颜色统计的中位数一分为二,使得得到的两个长方体所包含的像素个数相同,如下图所示:重复以上步骤,直到最后分割得到的长方体个数等于主题颜色的数量,最后取每个长方体的中点。在实际使用中,如果只按中点切,会出现某些长方体体积大但像素数少的情况。解决方法是在切割前先对长方体进行优先排序,排序系数为体积*像素数。这样就可以基本解决这类问题。效果代码首先创建一个canvas容器,将图片绘制到容器中。使用getImageData方法获取rgba。查看getImageData通过中值分割算法进行切分,提取颜色过滤掉相似的颜色。color.vue(以下代码为VUE3.0语法)importthemeColorfrom'../../components/colorExtraction';exportdefaultdefineComponent({setup(props){/***设置颜色的方法*/constSetColor=(colorArr:number[][])=>{//初始化和删除多余的子节点constextractColor=document.querySelector('#extract-color-id')asHTMLElement;while(extractColor.firstChild){extractColor.removeChild(extractColor.firstChild);}//创建子节点for(letindex=0;index{constimg=newImage();img.src=`ImageURL`;img.crossOrigin='anonymous';img.onload=()=>{themeColor(50,img,20,SetColor);};})colorExtraction.ts(以下代码为TypeScript语法,转换JavaScript删除所有类型定义即可)/***颜色框类**@param{Array}colorRange[[rMin,rMax],[gMin,gMax],[bMin,bMax]]颜色范围*@param{any}total总像素数,imageData/4*@param{any}data像素数据集*/classColorBox{colorRange:unknown[];总数;数据:Uint8ClampedArray;体积:数量;等级:数量;构造函数(colorRange:任何[],总计:数字,数据:Uint8ClampedArray){this.colorRange=colorRange;this.total=总计;这个。数据=数据;this.volume=(colorRange[0][1]-colorRange[0][0])*(colorRange[1][1]-colorRange[1][0])*(colorRange[2][1]-colorRange[2][0]);this.rank=total*this.volume;}getColor(){consttotal=this.total;constdata=this.data;让redCount=0,greenCount=0,blueCount=0;for(leti=0;i{//r:0,g:1,b:2constarr=[];for(leti=0;i<3;i++){arr.push(colorRange[i][1]-颜色范围[i][0]);}returnarr.indexOf(Math.max(arr[0],arr[1],arr[2]));}//切色范围constcutRange=(colorRange:number[][],colorSide:number,cutValue:任何)=>{constarr1:number[][]=[];constarr2:数字[][]=[];colorRange.forEach(function(item){arr1.push(item.slice());arr2.push(item.slice());})arr1[colorSide][1]=cutValue;arr2[colorSide][0]=cutValue;return[arr1,arr2];}//找到出现次数为中位数的颜色const__quickSort=(arr:any[]):any=>{if(arr.length<=1){returnarr;}constpivotIndex=Math.floor(arr.length/2);constpivot=arr.splice(pivotIndex,1)[0];常量左=[];const对=[];for(leti=0;i,total:number)=>{constarr=[];for(constkeyincolorCountMap){arr.push({color:parseInt(key),count:colorCountMap[key]})}constsortArr=__quickSort(arr);让medianCount=0;constmedianIndex=Math.floor(sortArr.length/2)for(leti=0;i<=medianIndex;i++){medianCount+=sortArr[i].count;}}return{color:parseInt(sortArr[medianIndex].color),count:medianCount}}//切色盒子constcutBox=(colorBox:{colorRange:number[][];total:number;data:Uint8ClampedArray})=>{constcolorRange=colorBox.colorRange;constcutSide=getCutSide(colorRange);constcolorCountMap:Record={};consttotal=colorBox.total;constdata=colorBox.data;//统计出各个值的数量for(leti=0;i{while(queue.length{returna.rank-b.rank});constcolorBox=queue.pop();constresult=cutBox(colorBox);queue=queue.concat(结果);}returnqueue.slice(0,num)}//颜色去重constcolorFilter=(colorArr:number[][],区别:数字)=>{for(leti=0;ivoid)=>{constcanvas=document.createElement('canvas')asHTMLCanvasElement;constctx=canvas.getContext('2d')asCanvasRenderingContext2D;让宽度=0让高度=0让imageData=nullcanvas.width=img。宽度为数字;宽度=canvas.width作为数字canvas.height=img.height作为数字height=canvas.heightctx.drawImage(img,0,0,width,height);imageData=ctx.getImageData(0,0,width,height).data;consttotal=imageData.length/4;让rMin=255,rMax=0,gMin=255,gMax=0,bMin=255,bMax=0;//获取范围for(leti=0;irMax){rMax=red;}如果(绿色gMax){gMax=green;}if(bluebMax){bMax=blue;}}constcolorRange=[[rMin,rMax],[gMin,gMax],[bMin,bMax]];缺点tcolorBox=newColorBox(colorRange,total,imageData);constcolorBoxArr=queueCut([colorBox],colorNumber);让colorArr=[];for(letj=0;j