鸿蒙Harmonyos实现,使用ImageKnife自定义transform来实现图片进度效果
import { Context } from '@ohos.abilityAccessCtrl';
import { image } from '@kit.ImageKit';
import { drawing } from '@kit.ArkGraphics2D';
import { GrayScaleTransformation, PixelMapTransformation } from '@ohos/imageknife';/*** 垂直进度灰度变换:顶部灰度,底部彩色,带波浪边界* @param grayRatio 顶部灰度区域占比 [0-1]* @param enableWave 是否启用波浪边界效果,false为直线分割(性能更佳)*/
@Sendable
export class VerticalProgressGrayscaleTransformation extends PixelMapTransformation {private readonly grayRatio: number;private readonly enableWave: boolean;private static readonly WAVE_AMPLITUDE_RATIO: number = 0.08; // 波浪振幅比例private static readonly WAVE_FREQUENCY: number = 2.5; // 波浪频率private static readonly WAVE_STEPS: number = 40; // 波浪平滑度(降低提升性能)constructor(grayRatio: number, enableWave: boolean = true) {super();this.grayRatio = Math.max(0, Math.min(1, grayRatio));this.enableWave = enableWave;}override async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {try {// 边界情况快速处理if (this.grayRatio <= 0.001) {return toTransform;}if (this.grayRatio >= 0.999) {return new GrayScaleTransformation().transform(context, toTransform, width, height);}// 获取实际图片尺寸const imageInfo = await toTransform.getImageInfo();if (!imageInfo.size) {return toTransform;}const actualWidth = imageInfo.size.width;const actualHeight = imageInfo.size.height;const grayHeight = Math.floor(actualHeight * this.grayRatio);// 如果灰度区域太小,直接返回原图if (grayHeight < 5) {return toTransform;}return this.applyVerticalGrayEffect(context, toTransform, actualWidth, actualHeight, grayHeight);} catch (err) {console.error('[VerticalProgressGrayscaleTransformation] Error:', err);return toTransform;}}/*** 应用垂直灰度效果(性能优化版)*/private async applyVerticalGrayEffect(context: Context,original: PixelMap,width: number,height: number,grayHeight: number): Promise<PixelMap> {try {// 创建结果PixelMap并复制原图const result = await this.createClonedPixelMap(original, width, height);// 创建灰度图const grayPixelMap = await new GrayScaleTransformation().transform(context, original, width, height);// 一次性完成Canvas操作const canvas = new drawing.Canvas(result);canvas.save();// 设置裁剪路径并绘制灰度区域canvas.clipPath(this.createOptimizedClipPath(width, grayHeight));canvas.drawImage(grayPixelMap, 0, 0, new drawing.SamplingOptions(drawing.FilterMode.FILTER_MODE_LINEAR));canvas.restore();return result;} catch (error) {console.error('[VerticalProgressGrayscaleTransformation] Apply effect error:', error);return original;}}/*** 创建克隆PixelMap(优化版)*/private async createClonedPixelMap(original: PixelMap, width: number, height: number): Promise<PixelMap> {const opts: image.InitializationOptions = {size: { width, height },pixelFormat: image.PixelMapFormat.RGBA_8888,editable: true,alphaType: image.AlphaType.PREMUL};const cloned = await image.createPixelMap(new ArrayBuffer(width * height * 4), opts);new drawing.Canvas(cloned).drawImage(original, 0, 0);return cloned;}/*** 创建优化的分割路径(波浪或直线)*/private createOptimizedClipPath(width: number, grayHeight: number): drawing.Path {const path = new drawing.Path();// 直线分割模式(高性能)if (!this.enableWave) {path.addRect({left: 0,top: 0,right: width,bottom: grayHeight});return path;}// 波浪分割模式const amplitude = Math.min(25, grayHeight * VerticalProgressGrayscaleTransformation.WAVE_AMPLITUDE_RATIO);// 波浪太小时使用直线if (amplitude < 2) {path.addRect({left: 0,top: 0,right: width,bottom: grayHeight});return path;}// 构建波浪路径path.moveTo(0, 0);path.lineTo(width, 0);path.lineTo(width, grayHeight);// 优化的波浪计算const steps = VerticalProgressGrayscaleTransformation.WAVE_STEPS;const stepWidth = width / steps;const waveFreq = VerticalProgressGrayscaleTransformation.WAVE_FREQUENCY;let prevX = width;let prevY = grayHeight;for (let i = 1; i <= steps; i++) {const x = width - i * stepWidth;const wavePhase = (i / steps) * Math.PI * 2 * waveFreq;const y = grayHeight + amplitude * Math.sin(wavePhase);// 使用二次贝塞尔曲线优化连接const controlX = (prevX + x) * 0.5;const controlY = (prevY + y) * 0.5;path.quadTo(controlX, controlY, x, y);prevX = x;prevY = y;}path.lineTo(0, 0);path.close();return path;}getKey(): string {return `VerticalProgressGray_${this.grayRatio}_${this.enableWave ? 'wave' : 'line'}`;}
}
Android实现,使用Glide自定义transform实现
public class VerticalProgressGrayscaleTransformation extends BitmapTransformation {private final float grayRatio; // 灰度区域高度占比,取值范围 [0, 1]public VerticalProgressGrayscaleTransformation(float grayRatio) {this.grayRatio = grayRatio;}@Overrideprotected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {int width = toTransform.getWidth();int height = toTransform.getHeight();Bitmap.Config config = toTransform.getConfig() != null ? toTransform.getConfig() : Bitmap.Config.ARGB_8888;Bitmap bitmap = pool.get(width, height, config);Canvas canvas = new Canvas(bitmap);// 1. 首先绘制完整的原始彩色图像作为底层canvas.drawBitmap(toTransform, 0, 0, null);// 对 grayRatio 进行边界处理,确保在 [0, 1] 范围内float clampedGrayRatio = Math.max(0f, Math.min(1f, this.grayRatio));// 只有当灰度占比大于一个极小值时才应用灰度效果if (clampedGrayRatio > 0.001f) {Paint grayPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 添加抗锯齿ColorMatrix matrix = new ColorMatrix(new float[]{0.299f, 0.587f, 0.114f, 0, 0,0.299f, 0.587f, 0.114f, 0, 0,0.299f, 0.587f, 0.114f, 0, 0,0, 0, 0, 1, 0,});grayPaint.setColorFilter(new ColorMatrixColorFilter(matrix));// 波浪的基准Y坐标,即灰色区域的平均下边界int waveBaseY = (int) (height * clampedGrayRatio);Path grayRegionPath = new Path();grayRegionPath.moveTo(0, 0); // 移动到左上角grayRegionPath.lineTo(width, 0); // 画到右上角// 定义波浪的振幅float amplitude;if (clampedGrayRatio <= 0.001f || clampedGrayRatio >= 0.999f) {amplitude = 0f; // 如果完全着色或完全灰色,则没有波浪} else {float baseAmplitude = height * 0.03f; // 基础振幅为图片高度的3%// 确保振幅不会使波浪超出图片顶部或底部amplitude = Math.min(baseAmplitude, waveBaseY);amplitude = Math.min(amplitude, height - waveBaseY);}// 从右向左绘制波浪线作为灰色区域的下边界grayRegionPath.lineTo(width, waveBaseY); // 连接到右侧波浪基准点int numCycles = 3; // 波浪周期数float waveLength = (float) width / numCycles; // 每个周期的长度float currentX = width;for (int i = 0; i < numCycles * 2; i++) { // 每个周期包含一个波峰和波谷,共 numCycles * 2段float nextX = Math.max(0, currentX - waveLength / 2); // 下一个X点float controlX = (currentX + nextX) / 2; // 控制点X坐标// 控制点Y坐标,交替形成波峰和波谷// 从右往左画,i为偶数时是波峰(相对基准线向上,Y值减小),奇数时是波谷(Y值增大)float controlY = waveBaseY + ((i % 2 == 0) ? -amplitude : amplitude);grayRegionPath.quadTo(controlX, controlY, nextX, waveBaseY); // 二阶贝塞尔曲线currentX = nextX;if (currentX == 0) {break; // 到达左边界}}grayRegionPath.lineTo(0, waveBaseY); // 确保连接到左侧波浪基准点grayRegionPath.close(); // 闭合路径,连接回 (0,0)// 保存画布状态canvas.save();// 将画布裁剪为波浪路径定义的区域canvas.clipPath(grayRegionPath);//在裁剪区域内,使用灰度画笔再次绘制原始图像canvas.drawBitmap(toTransform, 0, 0, grayPaint);// 恢复画布状态canvas.restore();}return bitmap;}@Overridepublic void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {messageDigest.update(("VerticalProgressGrayscaleTransformation" + grayRatio).getBytes());}
}