鸿蒙Next图像混合模式blendMode 原创

auhgnixgnahz
发布于 2025-9-16 10:00
浏览
0收藏

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) 源和目标像素的颜色值相乘。

属性演示:
鸿蒙Next图像混合模式blendMode-鸿蒙开发者社区
总结:
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)
    }
  }
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报
回复
    相关推荐