前言
首先,这篇文章的最终的效果不是很成功。记录一下我在这个失败过程中遇到的问题和尝试过的技术。
我想实现类似QQ音乐的如下效果
背景色是根据海报的主题色生成的,最后经过了高斯模糊。
刚开始考虑用js去实现,js肯定是可以实现但是尝试了几次效果不是很好。并且,对于计算像素点来说,还是在后端计算比较好。
这个过程我的理解:大概就是把图片分成 1px * 1px的像素点。计算出每个相同颜色像素点出现的次数。统计出最多的就是主题色。得到的是rgb颜色 rgb(255,255,255)
这种。
作为一个初级程序员👨🏻💻来说,对rgb颜色并不是很了解,特意去查了一下。
对于<image>
来说是无法去操作它的像素点的,通常情况下,要将其生成<canvas>
才能去读取图片数据。
我先尝试了在文件夹 📂 的html文件中读取文件夹中的图片。
【1】 在标签中画一个image
和 一个 canvas
<img id="scream" src="shuijiao.jpg"/>
<p>画布:</p>
<canvas id="myCanvas">
您的浏览器不支持 HTML5 canvas 标签。
</canvas>
【2】 获得图片 和 画布的 打印出来可以发现打印的是dom元素
getContext("2d") 是建立一个2维渲染的上下文 具体语法请看 ✈️
let img=document.getElementById("scream");
console.log(img)
let ctx=document.getElementById("myCanvas").getContext("2d");
【3】 对于读取图片来说,加载是缓慢的。所以需要 onload 来等待加载完成。 上一步创建了上下文 ,drawImage是将canvas图像源画到上下文。
img.onload=function(img,ctx){
ctx.drawImage(img,0,0,img.width,img.height);
console.log(ctx)
var imgData_obj = ctx.getImageData(0,0,250,150) // 获取画布上的图像像素矩阵
// var imgData = imgData_obj.data;
console.log(imgData_obj)
return ctx
}
我们说一下drawImage
它有 image sx sy sWidth sHeight dx dy dWidth dHeight 九个参数
语法: 参数个数可以使 3 5 9 ,注意对应参数都代表什么 详细的可以看一下✈️
void ctx.drawImage(image, dx, dy);
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
然后是getImageData
用来描述canvas隐含的像素数据
语法:参数分别是距离顶部的x轴距离、y轴距离,将要被提取区域的高、将要被提取区域的高。
tx.getImageData(sx, sy, sw, sh);
然后问题来了:画布是生成了 但是获取不到像素点的数据。一直报图片跨域问题。然后百度了很多这个错误还是解决的不了🙅🏻♀️。我觉得跟直接在文件夹中读取图片有些关系。索性也是试一试,还是去uniapp中写代码吧。
我开始的时候,像上面的写法,首先画一个图片 和 一个画布,然后通过getElementById获取元素。但是发现画布一直都没有画上,一直是白色的。
审查元素时发现,它会在canvas标签外还包了一层<uni-canvas>
标签。因此一直是画不上去的。
后来百度错误,无意中发现uni中有一系列处理同样过程的方法。
首先在methods中定义方法,然后再onLoad生命周期中去调用这个方法。我们在下面再去分析代码
getImage() {
// 获得图片信息
uni.getImageInfo({
// 注意图片的地址
src: "/static/rang.png",
success(res) {
console.log(res.path)
let imgHeight = 100;
let imgWidth =100;
var ctx = uni.createCanvasContext("logo") // 使用画布创建上下文 图片
ctx.drawImage(res.path, 0, 0, 100, 100) // 设置图片坐标及大小,括号里面的分别是(图片路径,x坐标,y坐标,width,height)
ctx.save(); //保存
ctx.draw(true, () => {
uni.canvasGetImageData({
canvasId: 'logo',
x: 0,
y: 0,
width: 100,
height: 100,
success: (res) => {
let data = res.data;
let arr = []
var r = 1,
g = 1,
b = 1;
// 取所有像素的平均值
for (var row = 0; row < imgHeight; row++) {
for (var col = 0; col < imgWidth; col++) {
// console.log(data[((img.width * row) + col) * 4])
if (row == 0) {
r += data[((imgWidth * row) + col)];
g += data[((imgWidth * row) + col) + 1];
b += data[((imgWidth * row) + col) + 2];
arr.push([r,g,b])
} else {
r += data[((imgWidth * row) + col) * 4];
g += data[((imgWidth * row) + col) * 4 + 1];
b += data[((imgWidth * row) + col) * 4 + 2];
arr.push([r,g,b])
}
}
}
console.log(arr[8000])
// 求取平均值
r /= (imgWidth * imgHeight);
g /= (imgWidth * imgHeight);
b /= (imgWidth * imgHeight);
// 将最终的值取整
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
let obj = {
r,
g,
b
}
console.log(obj)
},
fail: (fail) => {
console.log("ss")
}
})
})
}
})
},
官网: 💻
【作用】:获取图片信息
【类型】:是一个对象
【参数】:
success回调返回参数
【写法】
uni.getImageInfo({
src:"",
success(res){
}
})
我们在成功的回调函数中又使用了uni.createCanvasContext方法
【官网】:💻
【作用】:创建canvas绘图上下文,但是需要指定canvasid。
参数1:reserve 布尔型 非必填。是否接着上一次绘制,true为接着上一次绘制。如果为false,清除掉之前的绘制。
参数2:绘制完成的回调
绘制完成后我们通过uni.canvasGetImageData
来获得图片的数据
【官网】:💻
这个 canvasId 要对应上
uni.canvasGetImageData({
canvasId: 'log',
x: 0,
y: 0,
width: 100,
height: 100,
success(res) {
console.log(res.width) // 100
console.log(res.height) // 100
console.log(res.data instanceof Uint8ClampedArray) // true
console.log(res.data.length) // 100 * 100 * 4
}
})
最终我们根据像素点 求出了平均值
我们的图片如下:
计算出的rgb值如下[🌈颜色值转换]:
但是这涉及到了计算量,在前端做计算不是很好。而且现在的写法对于颜色较多的图片实现上不是很好。 所以打算再用python去实现
接下来用到了Python的PIL库。将处理之后的rgb颜色返回到前端,前端做为背景色后再使用高斯模糊。
先熟悉一下两个库
**[简介]**: Python Imaging Library,已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。
**[安装]**:
pip install pillow
**[API]**:
这是一个大佬对每个API的讲解✈️,我就不赘述了
[是什么]
是RGB (Red Green Blue) 色彩空间与三种其他色彩坐标系统 YIQ, HLS (Hue Lightness Saturation) 和 HSV (Hue Saturation Value) 表示的颜色值之间的双向转换。
所有这些色彩空间的坐标都使用浮点数值来表示。 在 YIQ 空间中,Y 坐标取值为[0,1],而 I 和 Q 坐标均可以为正数或负数。 在所有其他空间中,坐标取值均为 0 和 1 之间。 我们这里用到了把颜色从RGB值转为HSV值:colorsys.rgb_to_hsv(*r*, *g*, *b*)
这是这个库的文档✈️
因为我是在Django项目使用的,所以把这部份实现的代码从 view中拆分出来了,然后view中再去调用这个函数
[getImageBackground.py]
import colorsys
from PIL import Image
def get_dominant_color(image):
# 颜色模式转换,以便输出rgb颜色值
image = image.convert('RGBA')
# 生成缩略图,减少计算量,减小cpu压力
print(image.size)
# image.thumbnail((200, 200))
# 用于计算像素出现次数
max_score = 0
# 最后返回的rgb颜色
dominant_color = 0
for count, (r, g, b, a) in image.getcolors(image.size[0] * image.size[1]):
print(r,g,b,a)
# 跳过纯黑色
if a == 0:
continue
if r<45 and g<45 and b<45:
continue
saturation = colorsys.rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)[1]
y = min(abs(r * 2104 + g * 4130 + b * 802 + 4096 + 131072) >> 13, 235)
y = (y - 16.0) / (235 - 16)
# 忽略高亮色
if y > 0.9 or y<0.3:
continue
# Calculate the score, preferring highly saturated colors.
# Add 0.1 to the saturation so we don't completely ignore grayscale
# colors by multiplying the count by zero, but still give them a low
# weight.
score = (saturation + 0.1) * count
if score > max_score:
secondScore = max_score
max_score = score
dominant_color = (r, g, b)
return dominant_color
img = Image.open('./1.jpg') print("thumbnail前的尺寸", img.size) // 500px * 336px
img.thumbnail((128, 128)) // 128px * 86px
img.resize((128,128)) // 128px * 128px
views.py
import djangoProject.utils.getImageBackground as getBackground
导入写好的方法from django.http import HttpResponse,JsonResponse
import djangoProject.utils.getImageBackground as getBackground
import os
from PIL import Image
def my_view(request):
img = Image.open(r"./static/background/leaf.png")
rgb = getBackground.get_dominant_color(img)
return JsonResponse({"rgb1":rgb})
这里没有对前端的请求方式进行封装,直接使用uni.request
async request() {
let result = await uni.request({
url: 'http://127.0.0.1:8000/my_view/',
});
let [error, res] = result; //ES6对数组的解构
console.log(res)
if (res.statusCode === 200) {
let rgb = res.data.rgb;
this.rgb = `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`
console.log(this.rgb)
}
if (res.statusCode === 404) {
console.log('找不到接口资源');
}
}
我们第一次尝试的时候出现了跨域问题。在uniapp中跨域问题有些不好处理,我选择了在后端进行了跨域处理。
【安装】
pip install django-cors-headers
settings.py
【注册corsheaders】
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'first',
'corsheaders', // 添加到app中
'static'
]
【添加中间件】
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware', # 放到common前
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
接口返回值如下
:style="{}"
,this.rgb
是定义的变量filter: blur(20px);
给图像设置高斯模糊。"radius"一值设定高斯函数的标准差,或者是屏幕上以多少像素融在一起, 所以值越大越模糊;如果没有设定值,则默认是0;这个参数可设置css长度值,但不接受百分比值。<template>
<view class="content" :style="{'background-color':this.rgb,height:'100%'}">
</view>
</template>
<script>
export default {
data() {
return {
title: 'Hello',
rgb: ''
}
},
onLoad() {
this.request()
},
methods: {
async request() {
let result = await uni.request({
url: 'http://127.0.0.1:8000/my_view/',
});
let [error, res] = result; //ES6对数组的解构
console.log(res)
if (res.statusCode === 200) {
let rgb = res.data.rgb;
// 模板字符串
this.rgb = `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`
console.log(this.rgb)
}
if (res.statusCode === 404) {
console.log('找不到接口资源');
}
}
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
filter: blur(20px);
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
background-color: #f7fcfe;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
page {
uni-page-body {
background-color: #f7fcfe;
height: 100%;
font-size: 14px;
line-height: 1.8;
display: flex;
}
}
</style>
还算可以