
鸿蒙Next图像混合模式blendMode 原创
blendMode(value: BlendMode, type?: BlendApplyType)将当前控件的内容(包含子节点内容)与下方画布(可能为离屏画布)已有内容进行混合。
BlendMode,混合模式,定义当前内容和已有内容如何相互作用产生新的效果,不同的混合模式相互作用叠加的计算方式和效果也会对应有所不同。
BlendMode枚举计算公示中:s表示源像素,d表示目标像素;sa表示原像素透明度,da表示目标像素透明度;r表示混合后像素,ra表示混合后像素透明度
BlendApplyType.OFFSCREEN时,会创建当前组件大小的离屏画布,再将当前组件(含子组件)的内容绘制到离屏画布上,再用指定的混合模式与下方画布已有内容进行混合。
BlendMode部分混合模式
名称 | 公式 | 说明 |
---|---|---|
NONE | 将上层图像直接覆盖到下层图像上,不进行任何混合操作。 | |
CLEAR | r = 0 | 将源像素覆盖的目标像素清除为完全透明。 |
SRC | r = s | 只显示源像素。 |
DST | r = d | 只显示目标像素。 |
SRC_OVER | r = s + (1 - sa) * d | 将源像素按照透明度进行混合,覆盖在目标像素上。 |
DST_OVER | r = d + (1 - da) * s | 将目标像素按照透明度进行混合,覆盖在源像素上。 |
SRC_IN | r = s * da | 只显示源像素中与目标像素重叠的部分。 |
DST_IN | r = d * sa | 只显示目标像素中与源像素重叠的部分。 |
SRC_OUT | r = s * (1 - da) | 只显示源像素中与目标像素不重叠的部分。 |
DST_OUT | r = d * (1 - sa) | 只显示目标像素中与源像素不重叠的部分。 |
SRC_ATOP | r = s * da + d * (1 - sa) | 在源像素和目标像素重叠的地方绘制源像素,在源像素和目标像素不重叠的地方绘制目标像素。 |
DST_ATOP | r = d * sa + s * (1 - da) | 在源像素和目标像素重叠的地方绘制目标像素,在源像素和目标像素不重叠的地方绘制源像素。 |
XOR | r = s * (1 - da) + d * (1 - sa) | 仅显示源像素和目标像素中不重叠的部分。 |
PLUS | r = min(s + d, 1) | 源和目标像素的颜色值相乘。 |
属性演示:
总结:
1.观察公式或演示可知,当源像素和目标像素都为不透明时FAST模式下
CLEAR、DST_OUT效果一样
SRC_OUT、、XOR效果一样
SRC和SRC_OVER效果一样
DST和DST_OVER效果一样
SRC_IN和SRC_ATOP效果一样
DST_IN和DST_ATOP效果一样
因此,FAST模式下,前13种模式下我们不需要记住太多,只需要了解6个就可以
2.OFFSCREEN模式,是先创建了一个和源组件一样大小的画布,先把内容绘制到画布上,然后再和下层布局混合。
例如,Text组件在FAST模式下使用CLEAR模式,只是将文字内容所在的区域透明度变为0,而使用了离屏画布后,会在整个Text的底部增加一个不透明画布,这时候CLEAR模式会将整个Text区域透明度变为0
如果感兴趣可以复制源码运行起来,自己尝试一下各种模式的效果
演示代码:
@Extend(Radio)
function myRadioStyle() {
.checked(false)
.radioStyle({
checkedBackgroundColor: Color.Green, //开启状态底板颜色
uncheckedBorderColor:Color.Red, //关闭状态描边颜色
indicatorColor:Color.Yellow //开启状态内部圆饼颜色
})
.height(40)
.width(40)
}
@Entry
@ComponentV2
struct BlendModeTest {
@Provider('testBlendMode') testBlendMode:number=0
@Provider('testBlendMode2') testBlendMode2:number=0
@Local switchIsOn: boolean = false
@Local switchIsOn2: boolean = false
@Local testBlendType:number=0
@Local testBlendType2:number=0
@Local sa:number =1
@Local da:number =1
@Monitor('switchIsOn','switchIsOn2')
updateType(){
if (this.switchIsOn) {
this.testBlendType=BlendApplyType.OFFSCREEN
}else {
this.testBlendType=BlendApplyType.FAST
}
if (this.switchIsOn2) {
this.testBlendType2=BlendApplyType.OFFSCREEN
}else {
this.testBlendType2=BlendApplyType.FAST
}
}
build() {
Column() {
Stack() {
Stack() {
Text('HarmonyOS').fontSize(24).opacity(this.sa).fontColor(Color.White).width('60%').height('50%').blendMode(this.testBlendMode, this.testBlendType)
}.width('60%').height('50%').opacity(this.da).backgroundColor(Color.Red).blendMode(this.testBlendMode2, this.testBlendType2)
}.width('100%').height('30%').backgroundColor(Color.Yellow).blendMode(BlendMode.SRC_OVER, BlendApplyType.OFFSCREEN)
Scroll(){
Column({space:5}) {
Text('设置Text BlendModel')
Row(){
Text('是否离屏渲染')
Toggle({ type: ToggleType.Switch, isOn: this.switchIsOn })
.selectedColor('#007DFF') //打开状态下的背景颜色
.switchPointColor('#FFFFFF') //圆形滑块颜色
.onChange((isOn: boolean) => {
this.switchIsOn = isOn
})
}
Row({ space: 10 }) {
Text('原像素透明度sa:' + this.sa)
Slider({
value: this.sa,
min: 0,
max: 1,
step:0.1,
style: SliderStyle.OutSet
}).width('50%')
.onChange((value: number) => {
this.sa =Number.parseFloat(value.toFixed(1));
})
}
Row({space:5}) {
TestRadioItem({name:'NONE',mode:BlendMode.NONE})
TestRadioItem({name:'CLEAR',mode:BlendMode.CLEAR})
TestRadioItem({name:'SRC',mode:BlendMode.SRC})
TestRadioItem({name:'DST',mode:BlendMode.DST})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem({name:'SRC_OVER',mode:BlendMode.SRC_OVER})
TestRadioItem({name:'DST_OVER',mode:BlendMode.DST_OVER})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem({name:'SRC_IN',mode:BlendMode.SRC_IN})
TestRadioItem({name:'DST_IN',mode:BlendMode.DST_IN})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem({name:'SRC_OUT',mode:BlendMode.SRC_OUT})
TestRadioItem({name:'DST_OUT',mode:BlendMode.DST_OUT})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem({name:'SRC_ATOP',mode:BlendMode.SRC_ATOP})
TestRadioItem({name:'DST_ATOP',mode:BlendMode.DST_ATOP})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem({name:'XOR',mode:BlendMode.XOR})
TestRadioItem({name:'PLUS',mode:BlendMode.PLUS})
}.width('100%').justifyContent(FlexAlign.Center)
Text('设置红色区域 BlendModel')
Row(){
Text('是否离屏渲染')
Toggle({ type: ToggleType.Switch, isOn: this.switchIsOn2 })
.selectedColor('#007DFF') //打开状态下的背景颜色
.switchPointColor('#FFFFFF') //圆形滑块颜色
.onChange((isOn: boolean) => {
this.switchIsOn2 = isOn
})
}
Row({ space: 10 }) {
Text('标像素透明度da:' + this.da)
Slider({
value: this.da,
min: 0,
max: 1,
step:0.1,
style: SliderStyle.OutSet
}).width('50%')
.onChange((value: number) => {
this.da = Number.parseFloat(value.toFixed(1));
})
}
Row({space:5}) {
TestRadioItem2({name:'NONE',mode:BlendMode.NONE})
TestRadioItem2({name:'CLEAR',mode:BlendMode.CLEAR})
TestRadioItem2({name:'SRC',mode:BlendMode.SRC})
TestRadioItem2({name:'DST',mode:BlendMode.DST})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem2({name:'SRC_OVER',mode:BlendMode.SRC_OVER})
TestRadioItem2({name:'DST_OVER',mode:BlendMode.DST_OVER})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem2({name:'SRC_IN',mode:BlendMode.SRC_IN})
TestRadioItem2({name:'DST_IN',mode:BlendMode.DST_IN})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem2({name:'SRC_OUT',mode:BlendMode.SRC_OUT})
TestRadioItem2({name:'DST_OUT',mode:BlendMode.DST_OUT})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem2({name:'SRC_ATOP',mode:BlendMode.SRC_ATOP})
TestRadioItem2({name:'DST_ATOP',mode:BlendMode.DST_ATOP})
}.width('100%').justifyContent(FlexAlign.Center)
Row({space:10}) {
TestRadioItem2({name:'XOR',mode:BlendMode.XOR})
TestRadioItem2({name:'PLUS',mode:BlendMode.PLUS})
}.width('100%').justifyContent(FlexAlign.Center)
}.width('100%')
}.width('100%').layoutWeight(1)
}.backgroundColor(Color.Gray)
}
}
@ComponentV2
struct TestRadioItem{
@Consumer('testBlendMode') testBlendMode:number=0
@Require @Param name:string
@Require @Param mode:number
build() {
Row(){
Radio({ value: this.name, group: 'radioGroup' })
.myRadioStyle()
.onChange((isChecked: boolean) => {
if(isChecked)this.testBlendMode=this.mode
})
Text( this.name)
}
}
}
@ComponentV2
struct TestRadioItem2{
@Consumer('testBlendMode2') testBlendMode:number=0
@Require @Param name:string
@Require @Param mode:number
build() {
Row(){
Radio({ value: this.name, group: 'radioGroup2' })
.myRadioStyle()
.onChange((isChecked: boolean) => {
if(isChecked)this.testBlendMode=this.mode
})
Text( this.name)
}
}
}
