当前位置: 首页 > news >正文

uni-app开发小程序,根据图片提取主题色值

需求,在页面根据传入的图片提取图片主色值并用来设置区块背景色

<template><view class="icon-container"><view class="sport-icon" :style="{ backgroundColor: mainColor }"><image :src="'/static/images/sport/'+item.image" @load="handleImageLoad" class="li-img" mode="widthFix" /></view><view class="sport-right"><view><text class="sport-name">{{ item.name }}</text></view><view class="sport-text">{{ item.calorie }}千卡/{{ item.unit }}分钟</view></view><view class="align-self-end"><product-change :selected.sync="item.selected" @onChange="onChange" /></view><!-- Canvas 2D画布(隐藏) --><canvas type="2d" id="colorCanvas"style="position: absolute; left: -1000px; top: -1000px; width: 100px; height: 100px;"></canvas></view>
</template><script>import productChange from './product-change.vue'export default {name: 'productItem',components: {productChange},props: {name: {type: String,default: ''},item: {type: Object,default: () => {}}},data() {return {imgUrl: '@/static/images/sport/icon-sport-default.png', // 示例图片mainColor: '#ffffff', // 初始背景色textColor: '#000000', // 文字颜色,根据背景色自动调整canvas: null, // Canvas实例ctx: null // Canvas 2D上下文};},mounted() {console.log(this.item)// 初始化Canvas 2D上下文(在组件挂载后获取)this.initCanvas();},methods: {onChange() {this.$emit('onChange', {name: this.name,item: this.item})},// 初始化Canvas 2D上下文initCanvas() {// 通过ID获取Canvas实例(兼容uni-app的获取方式)const query = uni.createSelectorQuery().in(this);query.select('#colorCanvas').fields({node: true,size: true}).exec(res => {if (!res[0]) {console.error('未找到Canvas元素');return;}this.canvas = res[0].node;this.ctx = this.canvas.getContext('2d'); // 获取2D上下文// 设置Canvas尺寸(与样式尺寸一致)this.canvas.width = 100;this.canvas.height = 100;});},// 图片加载完成后触发handleImageLoad(e) {if (!this.canvas || !this.ctx) {console.error('Canvas未初始化完成');return;}const imgSrc = "/static/images/sport/" + this.item.image;// 获取图片信息(转为本地路径)uni.getImageInfo({src: imgSrc,success: (res) => this.drawToCanvas(res.path), // 绘制本地图片fail: (err) => {console.error('获取图片信息失败:', err);this.useDefaultColor();}});},// 绘制图片到Canvas(Canvas 2D方式)drawToCanvas(imagePath) {// 创建Image对象(Canvas 2D需要通过Image加载图片)const img = this.canvas.createImage();if (!imagePath.startsWith('/')) {imagePath = '/' + imagePath;}img.src = imagePath;// 图片加载完成后绘制到Canvasimg.onload = () => {// 清空画布(避免残留旧内容)this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);// 绘制图片(缩放到100x100,覆盖整个画布)this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height);// 延迟100ms后获取数据(确保绘制完成)setTimeout(() => this.extractMainColor(), 100);};// 图片加载失败处理img.onerror = (err) => {console.error('图片绘制失败:', err);this.useDefaultColor();};},// 提取主色extractMainColor() {try {// 读取Canvas像素数据(Canvas 2D的getImageData)const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);this.processImageData(imageData.data); // 处理像素数据} catch (err) {console.error('色值提取失败:{}', err);}},// 处理像素数据,计算主色processImageData(pixelData) {const colorFreq = {}; // 颜色出现频率let maxFreq = 0;let mainR = 255,mainG = 255,mainB = 255;// 遍历像素(每4个值为一组:R, G, B, A)for (let i = 0; i < pixelData.length; i += 4) {const r = pixelData[i];const g = pixelData[i + 1];const b = pixelData[i + 2];const a = pixelData[i + 3];// 忽略透明像素(透明度>50%的不统计)if (a < 128) continue;// 颜色量化(减少颜色种类,如每20阶合并一次)const key = `${Math.floor(r / 20)}-${Math.floor(g / 20)}-${Math.floor(b / 20)}`;colorFreq[key] = (colorFreq[key] || 0) + 1;// 记录出现频率最高的颜色if (colorFreq[key] > maxFreq) {maxFreq = colorFreq[key];mainR = r;mainG = g;mainB = b;}}// 设置主色和文字对比色this.mainColor = `rgb(${mainR}, ${mainG}, ${mainB},0.2)`;// 计算亮度(决定文字颜色)const luminance = (mainR * 299 + mainG * 587 + mainB * 114) / 1000;this.textColor = luminance > 130 ? '#000000' : '#ffffff';},// 使用默认颜色(失败时)useDefaultColor() {this.mainColor = '#f0f0f0';this.textColor = '#000000';}}}
</script><style lang="scss" scoped>.icon-container {border-bottom: 1px solid #F2F6FC;padding: 20rpx 40rpx;display: flex;}.li-img {// width: 55rpx;// height: 55rpx;}.sport-icon {width: 85rpx;height: 85rpx;padding: 15rpx;border-radius: 20rpx;}.sport-right {flex: 1;margin-left: 25rpx;width: 100%;}.sport-name {font-size: 32rpx;}.sport-text {color: #999;font-size: 26rpx;}.align-self-end {align-self: flex-end}.flex-end {display: flex;flex-direction: column;justify-content: flex-end;}
</style>

http://www.lryc.cn/news/594764.html

相关文章:

  • 网络编程基础:从 OSI 模型到 TCP/IP 协议族的全面解析
  • Android 中 SystemServiceManager 和 ServiceManager 的应用场景、区别与联系
  • 漏洞扫描 + 渗透测试:双轮驱动筑牢网络安全防线
  • Ubuntu 22.04 使用 Docker 安装 Redis 5 (安装包形式)
  • 内网与外网是通过什么进行传输的?内外网文件传输的安全方法
  • C#最佳实践:为何应尽量减少静态类的使用
  • 迅为八核高算力RK3576开发板摄像头实时推理测试 RetinaFace人脸检测
  • Curtain e-locker 易锁防泄密:无需网络隔离,实现安全与效率并存
  • 大腾智能国产3D CAD软件正式上架华为云云商店
  • 进程资源分配的安全性判断与安全序列
  • ZooKeeper学习专栏(四):单机模式部署与基础操作详解
  • 【c++】leetcode5 最长回文子串
  • 突破量子仿真瓶颈:微算法科技MLGO量子算法的算术化与核操作迭代模型
  • 飞算科技:以原创技术为翼,赋能产业数字化转型
  • Spring 中的 Bean 作用域(Scope)有哪些?各自适用于什么场景?
  • 江苏思必驰科技25Java实习面经
  • react class和function 如何模拟vue中的 双向绑定 监听 computed的方式
  • Component cannot be used as a JSX component
  • 芯谷科技--固定电压基准双运算放大器D4310
  • 杰和科技工业计算机AF208,打造高可靠新能源汽车检测产线
  • 杰发科技AC7840——硬件crc使用
  • 【烧脑算法】拓扑排序:从“依赖”到“序列”,理解题目中的先后逻辑
  • 5.6 framebuffer驱动
  • 录音转写:颠覆传统共享下载体验 | 如何提升团队效率?
  • 【前端状态更新与异步协调完全指南:React、Vue架构原理与复杂业务场景实战】
  • RustDesk自建服务器完整部署指南:从零开始到成功连接。成功解决rustdesk报错:未就绪,请检查网络连接
  • Vue 脚手架——render函数
  • 网络与信息安全有哪些岗位:(1)网络安全工程师
  • 【系统全面】Linux进程——基础知识介绍
  • 在本地WSL中的CentOS 7子系统中部署Ewomail邮件服务器