#跟着若城学鸿蒙# HarmonyOS NEXT学习之XComponent案例四:图像处理应用
·
一、案例介绍
本案例将展示如何使用XComponent组件实现图像处理功能,通过创建一个简单的图像编辑器,帮助开发者掌握XComponent在图像处理场景中的应用方法。
二、效果预览
在这个案例中,我们将实现一个基础的图像编辑器,支持亮度调节、对比度调整、滤镜效果等基本图像处理功能。
三、代码实现
1. 基本结构
@Entry
@Component
struct ImageProcessingExample {
private controller: XComponentController = new XComponentController()
private context: CanvasRenderingContext2D = null
private originalImageData: ImageData = null
@State currentFilter: string = 'none'
@State adjustments = {
brightness: 0,
contrast: 100,
saturation: 100
}
build() {
Column({ space: 16 }) {
// 工具栏
Row({ space: 16 }) {
Button('加载图片')
.onClick(() => {
this.loadImage()
})
Button('重置')
.onClick(() => {
this.resetAdjustments()
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
// 调整控制
Column({ space: 12 }) {
// 亮度调节
Row({ space: 8 }) {
Text('亮度')
.width(80)
Slider({
value: this.adjustments.brightness,
min: -100,
max: 100,
step: 1
})
.onChange((value: number) => {
this.adjustments.brightness = value
this.applyAdjustments()
})
}
// 对比度调节
Row({ space: 8 }) {
Text('对比度')
.width(80)
Slider({
value: this.adjustments.contrast,
min: 0,
max: 200,
step: 1
})
.onChange((value: number) => {
this.adjustments.contrast = value
this.applyAdjustments()
})
}
// 饱和度调节
Row({ space: 8 }) {
Text('饱和度')
.width(80)
Slider({
value: this.adjustments.saturation,
min: 0,
max: 200,
step: 1
})
.onChange((value: number) => {
this.adjustments.saturation = value
this.applyAdjustments()
})
}
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
// 滤镜选择
Row({ space: 16 }) {
ForEach(['none', 'grayscale', 'sepia', 'invert'], (filter: string) => {
Button(this.getFilterName(filter))
.onClick(() => {
this.currentFilter = filter
this.applyFilter()
})
.backgroundColor(this.currentFilter === filter ? '#007DFF' : '#DDDDDD')
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
// 图像显示区域
XComponent({
id: 'image_canvas',
type: 'surface',
libraryname: '',
controller: this.controller
})
.onLoad(() => {
this.initCanvas()
})
.width(350)
.height(350)
.backgroundColor(Color.White)
.borderRadius(8)
.border({
width: 1,
color: '#DDDDDD'
})
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#F8F8F8')
}
initCanvas() {
this.context = this.controller.getContext('2d')
if (this.context) {
// 绘制初始提示
this.context.fillStyle = '#666666'
this.context.font = '16px sans-serif'
this.context.textAlign = 'center'
this.context.fillText('点击"加载图片"按钮开始', 175, 175)
}
}
async loadImage() {
try {
// 模拟图片加载
const image = new Image()
image.src = 'data:image/png;base64,...' // 实际应用中替换为真实图片
image.onload = () => {
// 清空画布
this.context.clearRect(0, 0, 350, 350)
// 绘制图片并保持比例
const scale = Math.min(350 / image.width, 350 / image.height)
const x = (350 - image.width * scale) / 2
const y = (350 - image.height * scale) / 2
this.context.drawImage(
image,
x, y,
image.width * scale,
image.height * scale
)
// 保存原始图像数据
this.originalImageData = this.context.getImageData(0, 0, 350, 350)
}
} catch (error) {
console.error('Failed to load image:', error)
prompt.showToast({
message: '加载图片失败',
duration: 2000
})
}
}
resetAdjustments() {
this.adjustments.brightness = 0
this.adjustments.contrast = 100
this.adjustments.saturation = 100
this.currentFilter = 'none'
if (this.originalImageData) {
this.context.putImageData(this.originalImageData, 0, 0)
}
}
applyAdjustments() {
if (!this.originalImageData) return
// 创建临时canvas进行处理
const tempCanvas = new OffscreenCanvas(350, 350)
const tempContext = tempCanvas.getContext('2d')
tempContext.putImageData(this.originalImageData, 0, 0)
// 获取图像数据
const imageData = tempContext.getImageData(0, 0, 350, 350)
const data = imageData.data
// 应用调整
for (let i = 0; i < data.length; i += 4) {
// 转换为HSL
const [h, s, l] = this.rgbToHsl(data[i], data[i + 1], data[i + 2])
// 应用亮度调整
const newL = l * (1 + this.adjustments.brightness / 100)
// 应用对比度调整
const contrast = this.adjustments.contrast / 100
const newS = s * contrast
// 应用饱和度调整
const saturation = this.adjustments.saturation / 100
const finalS = newS * saturation
// 转换回RGB
const [r, g, b] = this.hslToRgb(h, Math.min(1, Math.max(0, finalS)), Math.min(1, Math.max(0, newL)))
data[i] = r
data[i + 1] = g
data[i + 2] = b
}
// 更新画布
this.context.putImageData(imageData, 0, 0)
// 应用当前滤镜
if (this.currentFilter !== 'none') {
this.applyFilter()
}
}
applyFilter() {
if (!this.originalImageData) return
const imageData = this.context.getImageData(0, 0, 350, 350)
const data = imageData.data
switch (this.currentFilter) {
case 'grayscale':
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3
data[i] = avg
data[i + 1] = avg
data[i + 2] = avg
}
break
case 'sepia':
for (let i = 0; i < data.length; i += 4) {
const r = data[i]
const g = data[i + 1]
const b = data[i + 2]
data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189))
data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168))
data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131))
}
break
case 'invert':
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]
data[i + 1] = 255 - data[i + 1]
data[i + 2] = 255 - data[i + 2]
}
break
}
this.context.putImageData(imageData, 0, 0)
}
getFilterName(filter: string): string {
switch (filter) {
case 'none': return '原图'
case 'grayscale': return '灰度'
case 'sepia': return '复古'
case 'invert': return '反色'
default: return filter
}
}
// RGB转HSL
rgbToHsl(r: number, g: number, b: number): [number, number, number] {
r /= 255
g /= 255
b /= 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
let h = 0
let s = 0
const l = (max + min) / 2
if (max !== min) {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) / d + 2
break
case b:
h = (r - g) / d + 4
break
}
h /= 6
}
return [h, s, l]
}
// HSL转RGB
hslToRgb(h: number, s: number, l: number): [number, number, number] {
let r = 0
let g = 0
let b = 0
if (s === 0) {
r = g = b = l
} else {
const hue2rgb = (p: number, q: number, t: number) => {
if (t < 0) t += 1
if (t > 1) t -= 1
if (t < 1/6) return p + (q - p) * 6 * t
if (t < 1/2) return q
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6
return p
}
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
const p = 2 * l - q
r = hue2rgb(p, q, h + 1/3)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1/3)
}
return [
Math.round(r * 255),
Math.round(g * 255),
Math.round(b * 255)
]
}
}
四、实现要点
1. 图像加载和显示
- 加载和绘制图像
- 保持图像比例
- 处理图像数据
2. 图像调整
- 亮度调节实现
- 对比度调整
- 饱和度控制
3. 滤镜效果
- 灰度转换
- 复古效果
- 反色处理
4. 颜色空间转换
- RGB与HSL转换
- 颜色调整算法
- 图像数据处理
五、扩展优化
- 图像裁剪
class CropTool {
private startX: number = 0
private startY: number = 0
private width: number = 0
private height: number = 0
private isDragging: boolean = false
startCrop(x: number, y: number) {
this.startX = x
this.startY = y
this.isDragging = true
}
updateCrop(x: number, y: number) {
if (!this.isDragging) return
this.width = x - this.startX
this.height = y - this.startY
}
finishCrop(context: CanvasRenderingContext2D) {
if (!this.isDragging) return
const imageData = context.getImageData(
this.startX,
this.startY,
this.width,
this.height
)
// 清空画布
context.clearRect(0, 0, context.canvas.width, context.canvas.height)
// 绘制裁剪后的图像
context.putImageData(imageData, 0, 0)
this.isDragging = false
}
}
- 图像旋转
rotateImage(degrees: number) {
if (!this.originalImageData) return
const tempCanvas = new OffscreenCanvas(350, 350)
const tempContext = tempCanvas.getContext('2d')
// 保存当前状态
tempContext.save()
// 移动到中心点
tempContext.translate(175, 175)
// 旋转
tempContext.rotate(degrees * Math.PI / 180)
// 绘制图像
tempContext.drawImage(
this.context.canvas,
-175, -175
)
// 恢复状态
tempContext.restore()
// 更新画布
this.context.clearRect(0, 0, 350, 350)
this.context.drawImage(tempCanvas, 0, 0)
}
六、总结
通过本案例,掌握XComponent开发图像处理应用的方法,提升开发能力。
更多推荐
所有评论(0)