由于我使用的操作系统无法安装ps,但是我也不想使用在线的替换工具替换,怕个人信息泄露,于是就想着能不能自己写一个工具出来实现相同的效果,最终我选择使用canvas的方式实现该效果
原理是通过img读取图片,并写入canvas,然后通过canvasContext提供的函数getImageData读取像素的rgba色值,通过对rgb色值进行区间判断,并设置成指定颜色,就可以实现简单的换背景效果了。
- 存在问题:边缘模糊色值无法分离,这个得后期研究一下
源码讲解
首先我们准备获取图片数据,并获得宽高
let imgWidth=0,imgHeight=0,imgPix=0;
const image = new Image()
image.src = IMG_URL
const {promise,resolve} = withResolvers<void>()
image.onload=()=>{
resolve?.(undefined)
}
await promise;
imgWidth = image.width
imgHeight=image.height
然后我们获取canva和canvascontext,并设置canvas宽度高度和context宽度和高度
const canvas = document.querySelector("#canvas") as HTMLCanvasElement
const context = canvas.getContext('2d') as CanvasRenderingContext2D
canvas.width = imgWidth
canvas.height = image.height
canvas.style.width = imgWidth / 10 + 'px'
canvas.style.height = imgHeight / 10 + 'px'
最后我们将图片写入canvas,并读取数据对比修改,和重新写入,实现图片改变背景色
context.drawImage(image, 0, 0, imgWidth, imgHeight)
const imgData = context.getImageData(0, 0, imgWidth, imgHeight)
const data = imgData.data
const step = 130; // 取值区间,可动态调整
for(let i = 0; i< data.length;i += 4) {
if(
filterColor[0]+50>data[i+0]&&filterColor[0]-50 <data[i+0]&&
filterColor[1]+step>data[i+1]&&filterColor[1]-step<data[i+1]&&
filterColor[2]+step>data[i+2]&&filterColor[2]-step<data[i+2]
) {
data[i+0] = 255
data[i+1] = 255
data[i+2] = 255
}
}
context.putImageData(imgData, 0, 0)
所使用的工具函数
- 串行promise
function withResolvers<T>(){
interface Obj{
promise?:Promise<T>,
resolve?:(value: T | PromiseLike<T>) => void
reject?:(value: T | PromiseLike<T>) => void
}
const obj:Obj = {}
obj.promise = new Promise<T>((res,rej)=>{
obj.resolve=res;
obj.reject=rej
})
return obj;
}
- 6位16进制色值转数值数组
function hexToNum(s:string){
let str = s
const colorArr:number[] = []
str = str.replace("#",'')
const strArr = str.split("")
if(strArr.length === 6) {
colorArr[0] = parseInt('0x' + strArr[0]+strArr[1])
colorArr[1] = parseInt('0x' + strArr[2]+strArr[3])
colorArr[2] = parseInt('0x' + strArr[4]+strArr[5])
}
return colorArr
}
完整代码
const IMG_URL = '/img/img.jpg'
function withResolvers<T>(){
interface Obj{
promise?:Promise<T>,
resolve?:(value: T | PromiseLike<T>) => void
reject?:(value: T | PromiseLike<T>) => void
}
const obj:Obj = {}
obj.promise = new Promise<T>((res,rej)=>{
obj.resolve=res;
obj.reject=rej
})
return obj;
}
function hexToNum(s:string){
let str = s
const colorArr:number[] = []
str = str.replace("#",'')
const strArr = str.split("")
if(strArr.length === 6) {
colorArr[0] = parseInt('0x' + strArr[0]+strArr[1])
colorArr[1] = parseInt('0x' + strArr[2]+strArr[3])
colorArr[2] = parseInt('0x' + strArr[4]+strArr[5])
}
return colorArr
}
async function main(){
const filterColor = hexToNum("#0097dc")
let imgWidth=0,imgHeight=0,imgPix=0;
const canvas = document.querySelector("#canvas") as HTMLCanvasElement
const context = canvas.getContext('2d') as CanvasRenderingContext2D
const image = new Image()
image.src = IMG_URL
const {promise,resolve} = withResolvers<void>()
image.onload=()=>{
resolve?.(undefined)
}
await promise;
imgWidth = image.width
imgHeight=image.height
imgPix = imgWidth/imgHeight
canvas.width = imgWidth
canvas.height = image.height
canvas.style.width = imgWidth / 10 + 'px'
canvas.style.height = imgHeight / 10 + 'px'
context.drawImage(image, 0, 0, imgWidth, imgHeight)
const imgData = context.getImageData(0, 0, imgWidth, imgHeight)
const data = imgData.data
const step = 130;
for(let i = 0; i< data.length;i += 4) {
if(
filterColor[0]+50>data[i+0]&&filterColor[0]-50 <data[i+0]&&
filterColor[1]+step>data[i+1]&&filterColor[1]-step<data[i+1]&&
filterColor[2]+step>data[i+2]&&filterColor[2]-step<data[i+2]
) {
data[i+0] = 255
data[i+1] = 255
data[i+2] = 255
}
}
context.putImageData(imgData, 0, 0)
document.body.appendChild(canvas)
}
window.onload=()=>{
main()
}
html
<body>
<canvas id="canvas"></canvas>
<script src="./src/index.ts"></script>
</body>
类形式书写
- 思考:图片和canvas1是两种不同的对象,下面这种方式只是满足能用,但是如果我们将其理解为资源和处理,就会发现这个可以使用桥接模式来实现
桥接模式定义:将对象的抽象部分和实现部分分离,使其能职责分离,独立变化(吃饭了,有时间再改)
function withResolvers<T>(){
interface Obj{
promise?:Promise<T>,
resolve?:(value: T | PromiseLike<T>) => void
reject?:(value: T | PromiseLike<T>) => void
}
const obj:Obj = {}
obj.promise = new Promise<T>((res,rej)=>{
obj.resolve=res;
obj.reject=rej
})
return obj;
}
function hexToNum(s:string){
let str = s
const colorArr:number[] = []
str = str.replace("#",'')
const strArr = str.split("")
if(strArr.length === 6) {
colorArr[0] = parseInt('0x' + strArr[0]+strArr[1])
colorArr[1] = parseInt('0x' + strArr[2]+strArr[3])
colorArr[2] = parseInt('0x' + strArr[4]+strArr[5])
}
return colorArr
}
class ChangeImgBC {
private image!:HTMLImageElement
private imageSrc = ''
private imageWidth=0
private imageHeight = 0
private canvas!:HTMLCanvasElement
private context!:CanvasRenderingContext2D
private filterColor:number[] = []
constructor(imageSrc: string,canvas:HTMLCanvasElement,filterColor:string) {
this.imageSrc = imageSrc
this.canvas = canvas
this.filterColor=hexToNum(filterColor)
this.init()
}
async init (){
await this.initImage()
this.initCanvas()
}
private async initImage(){
this.image =new Image()
this.image.src = this.imageSrc
const {promise,resolve} = withResolvers<void>()
this.image.onload=()=>{
resolve?.(undefined)
}
await promise;
this.imageWidth = this.image.width
this.imageHeight = this.image.height
}
private initCanvas() {
this.context = this.canvas.getContext('2d') as CanvasRenderingContext2D
this.canvas.width = this.imageWidth
this.canvas.height = this.imageHeight
this.canvas.style.width = this.imageWidth / 10 + 'px'
this.canvas.style.height = this.imageHeight / 10 + 'px'
}
public handler(){
this.context.drawImage(this.image, 0, 0, this.imageWidth, this.imageHeight)
const imgData = this.context.getImageData(0, 0, this.imageWidth, this.imageHeight)
const data = imgData.data
const step = 130;
for(let i = 0; i< data.length;i += 4) {
if(
this.filterColor[0]+50>data[i+0]&& this.filterColor[0]-50 <data[i+0]&&
this.filterColor[1]+step>data[i+1]&&this.filterColor[1]-step<data[i+1]&&
this.filterColor[2]+step>data[i+2]&&this.filterColor[2]-step<data[i+2]
) {
data[i+0] = 255
data[i+1] = 255
data[i+2] = 255
}
}
this.context.putImageData(imgData, 0, 0)
}
public getImg() {
return this.canvas.toDataURL("image/png")
}
}
环境
vite+node-16
目录结构
├─node_modules
├─public
│ └─img
├─index.d.ts
├─index.html
└─src
└─index.ts