完整源码:ImageFilterDemo 基于 HarmonyOS 5.0+,实现相册选图、11种滤镜实时预览、强度动态调节。
一、先看效果演示

二、ColorFilter 与 ImageFilter
鸿蒙提供了两种图像滤镜 API:本篇聚焦 ColorFilter
-
ColorFilter:作用于像素颜色,通过 4×5 颜色矩阵或混合模式实现。支持实时预览,GPU 渲染无延迟。典型效果:灰度、复古、反色、美白等。
-
ImageFilter:作用于图像本身,支持高斯模糊、偏移、组合等效果。需要离屏渲染,适合保存时处理。
三、颜色矩阵原理
颜色矩阵是一个 4×5 的数组,控制 RGBA 四个通道的输出:
R' = m00×R + m01×G + m02×B + m03×A + m04
G' = m10×R + m11×G + m12×B + m13×A + m14
B' = m20×R + m21×G + m22×B + m23×A + m24
A' = m30×R + m31×G + m32×B + m33×A + m34
矩阵结构说明:
- 4 行:分别对应输出通道 R'、G'、B'、A'
- 5 列:前 4 列对应输入通道 R、G、B、A 的系数,第 5 列是偏移量
各元素作用:
-
对角线元素(m00,m11,m22,m33):控制对应通道的强度
=1:保持原色>1:增强该通道<1:减弱该通道=0:完全移除该通道负数:反相该通道
-
非对角线元素:通道混合,将一个通道的值混入另一个通道(例如 m01 是绿色对红色的贡献)
-
最后一列(m04,m14,m24,m34) :亮度偏移(
>0提亮,<0压暗)
单位矩阵(原图):
[1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0]
对角线为 1,其余为 0,输出等于输入,保持原图不变。
四、架构设计
4.1 整体架构图
┌─────────────────────────────────────────────────────────────┐
│ UI 层 (Index.ets) │
│ 选图按钮 │ 图片预览 │ 滤镜列表 │ 强度滑块 │
└─────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 状态层 (@State) │
│ pixelMap | currentColorFilter | currentFilterId | intensity│
└─────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 滤镜管理层 (FilterRegistry) │
│ Map<string, IFilter> 滤镜注册与获取 │
└─────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 滤镜实现层 │
│ DynamicMatrixFilter │ LightingFilter │ LumaFilter │
│ ColorSpaceFilter │
└─────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 工具层 (FilterUtils) │
└─────────────────────────────────────────────────────────────┘
4.2 目录结构
最初设计每一种滤镜都是固定的,越写越多 colors 文件夹,通过动态矩阵我们可以完成所有的单滤镜效果。最终文件目录如下:
ImageFilterDemo/
├── pages/
│ └── Index.ets // 主界面
├── filter/
│ ├── core/
│ │ ├── IFilter.ets // 滤镜接口
│ │ ├── FilterRegistry.ets // 滤镜注册中心
│ │ └── FilterUtils.ets // 矩阵工具
│ └── colors/
│ ├── NoneFilter.ets // 原图
│ ├── DynamicMatrixFilter.ets // 动态矩阵滤镜
│ ├── LightingFilter.ets // 光照滤镜
│ ├── LumaFilter.ets // 亮度透明
│ └── ColorSpaceFilter.ets // 颜色空间转换
├── constants/
│ └── MatrixConstants.ets // 颜色矩阵常量
└── utils/
└── MediaHelper.ets // 相册选图
五、颜色矩阵常量定义
javascript
export class MatrixConstants {
// 灰度(平均值法)
// 原理:R' = G' = B' = (R+G+B)/3,三通道取平均值,消除色相
static readonly GRAY: number[] = [
0.333, 0.333, 0.333, 0, 0,
0.333, 0.333, 0.333, 0, 0,
0.333, 0.333, 0.333, 0, 0,
0, 0, 0, 1, 0
];
// 复古(棕褐色调)
// 原理:提高红/橙权重,降低蓝色,增加暖色偏移,模拟老照片
static readonly SEPIA: number[] = [
0.38, 0.62, 0.18, 0, 0.04,
0.32, 0.58, 0.12, 0, 0.02,
0.26, 0.54, 0.08, 0, 0.00,
0, 0, 0, 1, 0
];
// 反色
// 原理:R' = 1-R,G' = 1-G,B' = 1-B,偏移量1保证结果在[0,1]范围
static readonly INVERT: number[] = [
-1, 0, 0, 0, 1,
0, -1, 0, 0, 1,
0, 0, -1, 0, 1,
0, 0, 0, 1, 0
];
// 提亮
// 原理:每个RGB通道增加0.25(约64),整体变亮,色相不变
static readonly BRIGHTNESS: number[] = [
1, 0, 0, 0, 0.25,
0, 1, 0, 0, 0.25,
0, 0, 1, 0, 0.25,
0, 0, 0, 1, 0
];
// 美白
// 原理:增益1.1增强通道强度,偏移0.05提亮,使肤色更白
static readonly WHITEN: number[] = [
1.1, 0, 0, 0, 0.05,
0, 1.1, 0, 0, 0.05,
0, 0, 1.1, 0, 0.05,
0, 0, 0, 1, 0
];
// 高对比
// 原理:增益1.3使亮部更亮,偏移-0.12使暗部更暗,增强对比
static readonly CONTRAST: number[] = [
1.3, 0, 0, 0, -0.12,
0, 1.3, 0, 0, -0.12,
0, 0, 1.3, 0, -0.12,
0, 0, 0, 1, 0
];
}
六、动态强度调节
强度参数 t ∈ 0,1,在**单位矩阵(原图)和目标矩阵(完整效果)**之间线性插值:
矩阵_t = 原图 × (1-t) + 目标 × t
- t=0 → 原图
- t=1 → 完整滤镜效果
- t=0.5 → 50% 强度
javascript
import { MatrixConstants } from '../../constants/MatrixConstants';
export class FilterUtils {
/** 单位矩阵(原图) */
static identity(): number[] {
return [
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0
];
}
/** 组合多个滤镜 4x5 矩阵乘法: result = a * b */
static multiplyMatrix4x5(a: number[], b: number[]): number[] {
const result = new Array<number>(20).fill(0);
for (let row = 0; row < 4; row++) {
for (let col = 0; col < 5; col++) {
let sum = 0;
for (let i = 0; i < 5; i++) {
const aVal = a[row * 5 + i];
const bVal = i < 4 ? b[i * 5 + col] : (col === 4 ? b[i * 5 + col] : 0);
sum += aVal * bVal;
}
result[row * 5 + col] = sum;
}
}
return result;
}
/** 动态强度调节矩阵插值:lerp = a * (1 - t) + b * t */
static lerpMatrix(a: number[], b: number[], t: number): number[] {
t = Math.min(1, Math.max(0, t));
const result = new Array<number>(20);
for (let i = 0; i < 20; i++) {
result[i] = a[i] * (1 - t) + b[i] * t;
}
return result;
}
/** 强度可调的灰度矩阵 */
static createIntensityGray(intensity: number): number[] {
return FilterUtils.lerpMatrix(FilterUtils.identity(), MatrixConstants.GRAY, intensity);
}
/** 强度可调的复古矩阵 */
static createIntensitySepia(intensity: number): number[] {
return FilterUtils.lerpMatrix(FilterUtils.identity(), MatrixConstants.SEPIA, intensity);
}
/** 强度可调的反色矩阵 */
static createIntensityInvert(intensity: number): number[] {
const result = FilterUtils.lerpMatrix(FilterUtils.identity(), MatrixConstants.INVERT, intensity);
// 反色需要特殊处理偏移量
for (let i = 4; i < 20; i += 5) {
result[i] = result[i] * intensity;
}
return result;
}
/** 强度可调的提亮矩阵 */
static createIntensityBrightness(intensity: number): number[] {
const matrix = FilterUtils.identity();
const offset = 0.25 * intensity;
matrix[4] = offset;
matrix[9] = offset;
matrix[14] = offset;
return matrix;
}
/** 强度可调的高对比矩阵 */
static createIntensityContrast(intensity: number): number[] {
const gain = 1 + intensity * 0.3;
const offset = -0.12 * intensity;
return [
gain, 0, 0, 0, offset,
0, gain, 0, 0, offset,
0, 0, gain, 0, offset,
0, 0, 0, 1, 0
];
}
/** 强度可调的美白矩阵 */
static createIntensityWhiten(intensity: number): number[] {
return FilterUtils.lerpMatrix(FilterUtils.identity(), MatrixConstants.WHITEN, intensity);
}
/** 验证矩阵是否有效 */
static validateMatrix(matrix: number[]): boolean {
return matrix && matrix.length === 20 && matrix.every(v => isFinite(v));
}
}
七、核心代码实现
7.1 滤镜接口
javascript
import { drawing } from '@kit.ArkGraphics2D';
export interface IFilter {
getId(): string;
getName(): string;
getColorFilter(): ColorFilter | undefined;
getImageFilter(): drawing.ImageFilter | undefined;
isRealtime(): boolean;
}
7.2 动态矩阵滤镜
javascript
import { IFilter } from '../core/IFilter';
export class DynamicMatrixFilter implements IFilter {
private intensity: number = 1.0;
constructor(
private id: string,
private name: string,
private matrixGetter: (intensity: number) => number[]
) {}
getId(): string {
return this.id;
}
getName(): string {
return this.name;
}
// 动态滤镜不可以直接返回 matrix,需要返回新的数组对象,否则无法动态调节
getColorFilter(): ColorFilter | undefined {
const matrix = this.matrixGetter(this.intensity);
return [...matrix];
}
getImageFilter(): undefined {
return undefined;
}
isRealtime(): boolean {
return true;
}
setIntensity(intensity: number): void {
this.intensity = Math.min(1, Math.max(0, intensity));
}
getIntensity(): number {
return this.intensity;
}
}
7.3 原图滤镜
javascript
import { drawing } from '@kit.ArkGraphics2D';
import { IFilter } from '../core/IFilter';
export class NoneFilter implements IFilter {
getId(): string {
return 'none';
}
getName(): string {
return '原图';
}
getColorFilter(): ColorFilter | undefined {
return undefined;
}
getImageFilter(): drawing.ImageFilter | undefined {
return undefined;
}
isRealtime(): boolean {
return true;
}
}
7.4 光照滤镜
javascript
import { drawing, common2D } from '@kit.ArkGraphics2D';
import { IFilter } from '../core/IFilter';
export class LightingFilter implements IFilter {
private mulColor: common2D.Color | number;
private addColor: common2D.Color | number;
constructor(mulColor: common2D.Color | number, addColor: common2D.Color | number) {
this.mulColor = mulColor;
this.addColor = addColor;
}
getId(): string {
return 'lighting';
}
getName(): string {
return '光照';
}
getColorFilter(): ColorFilter | undefined {
return drawing.ColorFilter.createLightingColorFilter(this.mulColor, this.addColor);
}
getImageFilter(): drawing.ImageFilter | undefined {
return undefined;
}
isRealtime(): boolean {
return true;
}
}
7.5 亮度透明滤镜
javascript
import { drawing } from '@kit.ArkGraphics2D';
import { IFilter } from '../core/IFilter';
export class LumaFilter implements IFilter {
getId(): string {
return 'luma';
}
getName(): string {
return '亮度透明';
}
getColorFilter(): ColorFilter | undefined {
return drawing.ColorFilter.createLumaColorFilter();
}
getImageFilter(): drawing.ImageFilter | undefined {
return undefined;
}
isRealtime(): boolean {
return true;
}
}
7.6 颜色空间转换滤镜
javascript
import { drawing } from '@kit.ArkGraphics2D';
import { IFilter } from '../core/IFilter';
export enum ColorSpaceType {
LINEAR_TO_SRGB,
SRGB_TO_LINEAR
}
export class ColorSpaceFilter implements IFilter {
private type: ColorSpaceType;
constructor(type: ColorSpaceType) {
this.type = type;
}
getId(): string {
return this.type === ColorSpaceType.LINEAR_TO_SRGB ? 'linearToSrgb' : 'srgbToLinear';
}
getName(): string {
return this.type === ColorSpaceType.LINEAR_TO_SRGB ? '线性→SRGB' : 'SRGB→线性';
}
getColorFilter(): ColorFilter | undefined {
if (this.type === ColorSpaceType.LINEAR_TO_SRGB) {
return drawing.ColorFilter.createLinearToSRGBGamma();
} else {
return drawing.ColorFilter.createSRGBGammaToLinear();
}
}
getImageFilter(): drawing.ImageFilter | undefined {
return undefined;
}
isRealtime(): boolean {
return true;
}
}
7.7 滤镜注册中心
javascript
import { IFilter } from './IFilter';
import { LightingFilter } from '../colors/LightingFilter';
import { LumaFilter } from '../colors/LumaFilter';
import { ColorSpaceFilter, ColorSpaceType } from '../colors/ColorSpaceFilter';
import { NoneFilter } from '../colors/NoneFilter';
import { FilterUtils } from './FilterUtils';
import { DynamicMatrixFilter } from '../colors/DynamicMatrixFilter';
export class FilterRegistry {
private static filters: Map<string, IFilter> = new Map();
static initialize() {
// 原图
FilterRegistry.register(new NoneFilter());
FilterRegistry.register(new DynamicMatrixFilter('gray', '灰度', (t) => FilterUtils.createIntensityGray(t)));
FilterRegistry.register(new DynamicMatrixFilter('sepia', '复古', (t) => FilterUtils.createIntensitySepia(t)));
FilterRegistry.register(new DynamicMatrixFilter('invert', '反色', (t) => FilterUtils.createIntensityInvert(t)));
FilterRegistry.register(new DynamicMatrixFilter('brightness', '提亮', (t) => FilterUtils.createIntensityBrightness(t)));
FilterRegistry.register(new DynamicMatrixFilter('contrast', '高对比', (t) => FilterUtils.createIntensityContrast(t)));
FilterRegistry.register(new DynamicMatrixFilter('whiten', '美白', (t) => FilterUtils.createIntensityWhiten(t)));
// 特效滤镜
FilterRegistry.register(new LightingFilter(0xFFCCCCCC, 0x00222222));
FilterRegistry.register(new LumaFilter());
// 颜色空间转换滤镜
FilterRegistry.register(new ColorSpaceFilter(ColorSpaceType.LINEAR_TO_SRGB));
FilterRegistry.register(new ColorSpaceFilter(ColorSpaceType.SRGB_TO_LINEAR));
}
private static register(filter: IFilter) {
FilterRegistry.filters.set(filter.getId(), filter);
}
static getAll(): IFilter[] {
if (FilterRegistry.filters.size === 0) {
FilterRegistry.initialize();
}
return Array.from(FilterRegistry.filters.values());
}
static get(id: string): IFilter | undefined {
if (FilterRegistry.filters.size === 0) {
FilterRegistry.initialize();
}
return FilterRegistry.filters.get(id);
}
}
7.8 相册选图工具
javascript
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { image } from '@kit.ImageKit';
import fs from '@ohos.file.fs';
export class MediaHelper {
// 读取图片
static async pickImage(): Promise<image.PixelMap | null> {
const picker = new photoAccessHelper.PhotoViewPicker();
try {
const result = await picker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 1
});
if (result.photoUris.length === 0) {
return null;
}
const uri = result.photoUris[0];
const file = await fs.open(uri, fs.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(file.fd);
const pixelMap = await imageSource.createPixelMap();
await imageSource.release();
await fs.close(file);
return pixelMap;
} catch (error) {
return null
}
}
}
7.9 主界面
javascript
import { IFilter } from '../filter/core/IFilter';
import { MediaHelper } from '../utils/MediaHelper';
import promptAction from '@ohos.promptAction';
import { image } from '@kit.ImageKit';
import { FilterRegistry } from '../filter/core/FilterRegistry';
import { DynamicMatrixFilter } from '../filter/colors/DynamicMatrixFilter';
import { LengthMetrics } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State pixelMap: image.PixelMap | null = null;
@State currentColorFilter: ColorFilter | undefined = undefined;
@State currentFilterId: string = 'none';
@State currentFilter: IFilter | undefined = undefined;
@State currentIntensity: number = 1.0;
@State filterList: IFilter[] = FilterRegistry.getAll();
aboutToAppear(): void {
// 初始化当前滤镜
this.currentFilter = FilterRegistry.get('none');
this.currentColorFilter = this.currentFilter?.getColorFilter();
}
// 切换滤镜
private switchFilter(filter: IFilter): void {
console.info(`[Index] switchFilter: ${filter.getId()}, isDynamic: ${filter instanceof DynamicMatrixFilter}`);
if (!filter.isRealtime()) {
promptAction.showToast({ message: '该滤镜仅支持离屏渲染,预览无效果' });
return;
}
this.currentFilterId = filter.getId();
this.currentFilter = filter;
this.currentColorFilter = filter.getColorFilter();
if (filter instanceof DynamicMatrixFilter) {
this.currentIntensity = filter.getIntensity();
} else {
this.currentIntensity = 1.0;
}
}
// 调节强度
private changeIntensity(value: number): void {
console.info(`[Index] changeIntensity called, value: ${value}`);
const filter = this.currentFilter;
if (filter instanceof DynamicMatrixFilter) {
filter.setIntensity(value);
this.currentColorFilter = filter.getColorFilter();
this.currentIntensity = value;
}
}
build() {
Column({ space: 16 }) {
// 图片预览区
if (this.pixelMap) {
Image(this.pixelMap)
.width('100%')
.layoutWeight(1)
.objectFit(ImageFit.Contain)
.colorFilter(this.currentColorFilter)
.borderRadius(12)
.margin({ top: 8, bottom: 8 })
.backgroundColor('#F0F0F0')
} else {
Column() {
Text('暂无图片')
.fontSize(16)
.fontColor('#999')
Text('请点击下方按钮选择图片')
.fontSize(12)
.fontColor('#BBB')
.margin({ top: 8 })
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('#F5F5F5')
.borderRadius(12)
.border({ width: 1, color: '#E0E0E0', style: BorderStyle.Dashed })
}
// 强度滑块
if (this.pixelMap && this.currentFilter instanceof DynamicMatrixFilter) {
Column() {
Row() {
Text('强度')
.fontSize(14)
.fontColor('#666')
Blank()
Text(`${Math.round(this.currentIntensity * 100)}%`)
.fontSize(14)
.fontColor('#3B82F6')
}
.width('100%')
.margin({ bottom: 8 })
Slider({
value: this.currentIntensity,
min: 0,
max: 1,
step: 0.01
})
.width('100%')
.onChange((value: number) => {
this.changeIntensity(value);
})
}
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor('#F8F9FA')
.borderRadius(12)
.margin({ bottom: 8 })
}
// 滤镜列表
Flex({
space: {
main: LengthMetrics.vp(10),
cross: LengthMetrics.vp(10)
},
direction: FlexDirection.Row,
wrap: FlexWrap.Wrap,
alignContent: FlexAlign.Start,
}) {
ForEach(this.filterList, (filter: IFilter) => {
Text(filter.getName())
.fontSize(14)
.padding({ left: 14, right: 14, top: 8, bottom: 8 })
.borderRadius(24)
.backgroundColor(this.currentFilterId === filter.getId() ? '#3B82F6' : '#F3F4F6')
.fontColor(this.currentFilterId === filter.getId() ? '#FFFFFF' : '#374151')
.opacity(filter.isRealtime() ? 1.0 : 0.7)
.onClick(() => {
this.switchFilter(filter);
})
}, (filter: IFilter) => filter.getId())
}
.padding({ left: 16, right: 16 })
.width('100%')
.height(150)
Row({ space: 12 }) {
Button('从相册选择图片')
.layoutWeight(1)
.height(44)
.onClick(async () => {
const p = await MediaHelper.pickImage();
if (p) {
this.pixelMap = p;
// 选图后重新应用当前滤镜
if (this.currentFilter && this.currentFilter.isRealtime()) {
this.currentColorFilter = this.currentFilter.getColorFilter();
}
} else {
promptAction.showToast({ message: '未选择图片' });
}
})
}
.width('100%')
.padding({ top: 8 })
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor(Color.White)
}
}
八、总结
本篇完整实现了图片颜色滤镜,涵盖 11 种 ColorFilter 滤镜效果,代码已发布可直接下载运行查看。
核心分享:
- 通过 4×5 颜色矩阵,掌握了对 RGBA 通道值的修改即可改变图片颜色
- ColorFilter 基于 GPU 渲染,切换滤镜无延迟
- 矩阵插值技术实现 0-100% 动态强度调节
后续可扩展:
- 自定义矩阵参数,创造更多色彩效果
- 接入 ImageFilter 实现模糊、偏移等高级滤镜
- 增加离屏渲染,支持保存带滤镜的图片
如果觉得本文对你有帮助,请点赞、收藏、转发支持!