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;
				// 图片加载完成后绘制到Canvas
				img.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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值