支持多种组合变换

Signed-off-by: zhang_hanyong <zhang_hanyong@h-partners.com>
This commit is contained in:
zhang_hanyong 2024-05-27 16:11:30 +08:00 committed by Madi
parent 27094f0db9
commit 52e5d1f5bf
34 changed files with 1886 additions and 55 deletions

View File

@ -1,3 +1,6 @@
## 3.0.0-rc.6
- 支持多种组合变换
## 3.0.0-rc.5
- 图片加载事件增加请求开始的回调,以及修复有缓存时没有回调的bug
- 修复对已销毁组件不再下发请求的逻辑

View File

@ -147,6 +147,26 @@ ImageKnifeComponent({ ImageKnifeOption:
}
}).width(100).height(100)
```
多种组合变换用法
```
let transformations: collections.Array<PixelMapTransformation> = new collections.Array<PixelMapTransformation>();
transformations.push(new BlurTransformation(5));
transformations.push(new BrightnessTransformation(0.2));
ImageKnifeComponent({
{
loadSrc: $r('app.media.pngSample'),
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Contain,
border: { radius: { topLeft: 50, bottomRight: 50 } }, // 圆角设置
transformation: transformations.length > 0 ? new MultiTransTransformation(transformations) : undefined // 图形变换组
}
}).width(300)
.height(300)
.rotate({ angle: 90 }) // 旋转90度
.contrast(12) // 对比度滤波器
```
#### 8.监听图片加载成功与失败
@ -210,7 +230,38 @@ ImageKnifeComponent({ ImageKnifeOption:
| putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | 写入内存磁盘缓存 |
| removeMemoryCache| url: string | ImageKnifeOption | 清理指定内存缓存 |
| removeFileCache | url: string | ImageKnifeOption | 清理指定磁盘缓存 |
### 图形变换类型需要为GPUImage添加依赖项
| 类型 | 相关描述 |
| ---------------------------------- | ----------------------------- |
| BlurTransformation | 模糊处理 |
| BrightnessTransformation | 亮度滤波器 |
| CropCircleTransformation | 圆形剪裁显示 |
| CropCircleWithBorderTransformation | 圆环展示 |
| CropSquareTransformation | 正方形剪裁 |
| CropTransformation | 自定义矩形剪裁 |
| GrayScaleTransformation | 灰度级滤波器 |
| InvertTransformation | 反转滤波器 |
| KuwaharaTransformation | 桑原滤波器使用GPUIImage |
| MaskTransformation | 遮罩 |
| PixelationTransformation | 像素化滤波器使用GPUIImage |
| SepiaTransformation | 乌墨色滤波器使用GPUIImage |
| SketchTransformation | 素描滤波器使用GPUIImage |
| SwirlTransformation | 扭曲滤波器使用GPUIImage |
| ToonTransformation | 动画滤波器使用GPUIImage |
| VignetterTransformation | 装饰滤波器使用GPUIImage |
## 下载安装GPUImage依赖
方法一在Terminal窗口中执行如下命令安装三方包DevEco Studio会自动在工程的oh-package.json5中自动添加三方包依赖。
```
ohpm install @ohos/gpu_transform
```
方法二: 在工程的oh-package.json5中设置三方包依赖配置示例如下
```
"dependencies": {
"@ohos/gpu_transform": "^1.0.2"
}
```
## 约束与限制
在下述版本验证通过:

View File

@ -1,82 +1,417 @@
import { BlurTransformation, BrightnessTransformation, ImageKnifeComponent, ImageKnifeOption,
import {
BlurTransformation,
BrightnessTransformation,
CropCircleTransformation,
CropCircleWithBorderTransformation,
CropSquareTransformation,
CropTransformation,
GrayScaleTransformation,
ImageKnifeComponent,
ImageKnifeOption,
InvertTransformation,
KuwaharaTransformation,
MaskTransformation,
MultiTransTransformation,
PixelMapTransformation } from '@ohos/libraryimageknife'
PixelationTransformation,
PixelMapTransformation,
SepiaTransformation,
SketchTransformation,
SwirlTransformation,
ToonTransformation,
VignetterTransformation
} from '@ohos/imageknife';
import { collections } from '@kit.ArkTS'
@Entry
@Component
struct ImageTransformation {
@State imageKnifeOption: ImageKnifeOption = {
loadSrc: "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg",
loadSrc: $r('app.media.pngSample'),
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Contain
}
@State isRound: boolean = false;
@State isContrast: boolean = false;
@State isRotate: boolean = false;
isBlur: boolean = false
isBrightness: boolean = false
isGrayScale: boolean = false;
isInvert: boolean = false;
isToon: boolean = false;
isCropCircle: boolean = false;
isCropCircleWithBorder: boolean = false;
isKuwahara: boolean = false;
isPixelation: boolean = false;
isSketch: boolean = false;
isSwirl: boolean = false;
isVignetter: boolean = false;
isCropSquare: boolean = false;
isCropTop: boolean = false;
isCropCenter: boolean = false;
isCropBottom: boolean = false;
isMask: boolean = false;
isSepia: boolean = false;
build() {
Column() {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox1', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isBlur = value
this.upedateImageKnifeOption()
})
.width(30)
.height(30)
Text('模糊效果').fontSize(20)
}
Scroll() {
Column() {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox1', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isBlur = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('模糊效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox2', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isBrightness = value
this.upedateImageKnifeOption()
})
.width(30)
.height(30)
Text('高亮效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox2', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isBrightness = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('高亮效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox3', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
console.info('Checkbox3 change is' + value)
})
.width(30)
.height(30)
Text('Checkbox3').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox3', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isGrayScale = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('灰化效果').fontSize(20)
}
ImageKnifeComponent({
imageKnifeOption: this.imageKnifeOption
}).width(300).height(300)
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox4', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isInvert = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('反转效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox5', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isToon = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('动画滤镜效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox6', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isCropCircle = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('裁剪圆形效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox7', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isCropCircleWithBorder = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('裁剪圆形带边框效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox8', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isContrast = value;
})
.width(30)
.height(30)
Text('对比度效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox9', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isSepia = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('乌墨色滤波效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox10', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isRotate = value;
})
.width(30)
.height(30)
Text('旋转效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox11', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isRound = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('圆角效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox12', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isKuwahara = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('桑原滤波效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox13', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isPixelation = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('像素化滤波效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox14', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isSketch = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('素描滤波效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox15', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isSwirl = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('扭曲滤波效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox16', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isVignetter = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('装饰滤波效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox17', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isCropSquare = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('正方形裁剪效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox18', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isCropTop = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('上方裁剪效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox19', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isCropCenter = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('中间裁剪效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox20', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isCropBottom = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('底下裁剪效果').fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'checkbox21', group: 'checkboxGroup' })
.selectedColor(0x39a2db)
.shape(CheckBoxShape.ROUNDED_SQUARE)
.onChange((value: boolean) => {
this.isMask = value;
this.updateImageKnifeOption();
})
.width(30)
.height(30)
Text('遮罩效果').fontSize(20)
}
if (this.isContrast) {
ImageKnifeComponent({
imageKnifeOption: this.imageKnifeOption
})
.width(300)
.height(300)
.rotate({ angle: this.isRotate ? 90 : 0 })
.contrast(12)
.backgroundColor(Color.Pink)
} else {
ImageKnifeComponent({
imageKnifeOption: this.imageKnifeOption
}).width(300)
.height(300)
.rotate({ angle: this.isRotate ? 90 : 0 })
.backgroundColor(Color.Pink)
}
}
}
.height('100%')
.width('100%')
}
upedateImageKnifeOption() {
updateImageKnifeOption() {
let transformations: collections.Array<PixelMapTransformation> = new collections.Array<PixelMapTransformation>()
if (this.isBlur){
transformations.push(new BlurTransformation(5))
if (this.isBlur) {
transformations.push(new BlurTransformation(5));
}
if (this.isBrightness){
transformations.push(new BrightnessTransformation(0.2))
if (this.isBrightness) {
transformations.push(new BrightnessTransformation(0.2));
}
if (this.isGrayScale) {
transformations.push(new GrayScaleTransformation());
}
if (this.isInvert) {
transformations.push(new InvertTransformation());
}
if (this.isToon) {
transformations.push(new ToonTransformation(0.3, 10.0));
}
if (this.isCropCircle) {
transformations.push(new CropCircleTransformation());
}
if (this.isCropCircleWithBorder) {
transformations.push(new CropCircleWithBorderTransformation(16, { r_color: 100, g_color: 100, b_color: 0 }));
}
if (this.isKuwahara) {
transformations.push(new KuwaharaTransformation(10));
}
if (this.isPixelation) {
transformations.push(new PixelationTransformation(5.0));
}
if (this.isSketch) {
transformations.push(new SketchTransformation());
}
if (this.isSwirl) {
transformations.push(new SwirlTransformation(200, 1.0, [0.5, 0.5]));
}
if (this.isVignetter) {
transformations.push(new VignetterTransformation([0.5, 0.5], [0.0, 0.0, 0.0], [0.3, 0.75]));
}
if (this.isCropSquare) {
transformations.push(new CropSquareTransformation());
}
if (this.isCropTop) {
transformations.push(new CropTransformation(25, 25, 0));
}
if (this.isCropCenter) {
transformations.push(new CropTransformation(25, 25, 1));
}
if (this.isCropBottom) {
transformations.push(new CropTransformation(25, 25, 2));
}
if (this.isSepia) {
transformations.push(new SepiaTransformation());
}
if (this.isMask) {
transformations.push(new MaskTransformation($r('app.media.mask_starfish')));
}
this.imageKnifeOption = {
loadSrc: "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg",
loadSrc: $r('app.media.pngSample'),
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Contain,
border: { radius: this.isRound ? { topLeft: 50, bottomRight: 50 } : 0 },
transformation: transformations.length > 0 ? new MultiTransTransformation(transformations) : undefined
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -17,6 +17,7 @@ import FileLruCacheTest from './FileLruCache.test';
import ImageKnifeOptionTest from './ImageKnifeOption.test';
import MemoryLruCacheTest from './MemoryLruCache.test';
import ImageKnifeTest from './ImageKnife.test';
import Transform from './transform.test';
export default function testsuite() {
MemoryLruCacheTest();
@ -24,4 +25,5 @@ export default function testsuite() {
DefaultJobQueueTest();
ImageKnifeOptionTest();
ImageKnifeTest();
Transform();
}

View File

@ -0,0 +1,226 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'
import {
BlurTransformation,
BrightnessTransformation,
CropCircleTransformation,
CropCircleWithBorderTransformation,
CropSquareTransformation,
CropTransformation,
GrayScaleTransformation,
InvertTransformation,
KuwaharaTransformation,
MaskTransformation,
PixelationTransformation,
SepiaTransformation,
SketchTransformation,
SwirlTransformation,
ToonTransformation,
VignetterTransformation
} from '@ohos/imageknife'
const BASE_COUNT: number = 1000;
export default function Transform() {
describe('Transform', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
})
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
})
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
})
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
})
it('TestBlurTransformation', 0, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new BlurTransformation(15);
}
endTime(startTime, 'TestBlurTransformation');
let blur = new BlurTransformation(15);
expect(blur.getName()).assertEqual('BlurTransformation;radius:15');
})
it('TestBrightnessTransformation', 1, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new BrightnessTransformation(20);
}
endTime(startTime, 'BrightnessTransformation');
let bright = new BrightnessTransformation(20);
expect(bright.getName()).assertEqual("BrightnessTransformation;bright:20");
})
it('TestCropCircleTransformation', 3, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new CropCircleTransformation();
}
endTime(startTime, 'TestCropCircleTransformation');
let cropCircle = new CropCircleTransformation();
expect(cropCircle.getName()).assertContain("CropCircleTransformation");
expect(cropCircle.getName()).assertContain(";mCenterX:");
expect(cropCircle.getName()).assertContain(";mCenterY:");
expect(cropCircle.getName()).assertContain(";mRadius:");
})
it('TestCropCircleWithBorderTransformation', 4, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new CropCircleWithBorderTransformation(10, {
r_color: 100, g_color: 100, b_color: 100
});
}
endTime(startTime, 'TestCropCircleWithBorderTransformation');
let CropCircleWithBorder = new CropCircleWithBorderTransformation(10, {
r_color: 100, g_color: 100, b_color: 100
});
expect(CropCircleWithBorder.getName()).assertContain("CropCircleWithBorderTransformation")
expect(CropCircleWithBorder.getName()).assertContain(";mCenterX:");
expect(CropCircleWithBorder.getName()).assertContain(";mCenterY:");
expect(CropCircleWithBorder.getName()).assertContain(";mRadius:");
expect(CropCircleWithBorder.getName()).assertContain(";mBorderSize:");
expect(CropCircleWithBorder.getName()).assertContain(";mRColor:");
expect(CropCircleWithBorder.getName()).assertContain(";mGColor:");
expect(CropCircleWithBorder.getName()).assertContain(";mBColor:");
})
it('TestCropSquareTransformation', 5, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new CropSquareTransformation();
}
endTime(startTime, 'TestCropSquareTransformation');
let CropSquare = new CropSquareTransformation();
expect(CropSquare.getName()).assertContain("CropSquareTransformation");
})
it('TestCropTransformation', 6, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new CropTransformation(10, 10, 1);
}
endTime(startTime, 'TestCropTransformation');
let crop = new CropTransformation(10, 10, 1);
expect(crop.getName()).assertContain("CropTransformation" + ";mWidth:10" + ";mHeight:10" + ";mCropType:1")
})
it('TestGrayScaleTransformation', 7, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new GrayScaleTransformation();
}
endTime(startTime, 'GrayScaleTransformation');
let grayscale = new GrayScaleTransformation();
expect(grayscale.getName()).assertContain("GrayScaleTransformation")
})
it('TestInvertTransformation', 8, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new InvertTransformation();
}
endTime(startTime, 'TestInvertFilterTransformation');
let invert = new InvertTransformation();
expect(invert.getName()).assertContain("InvertTransformation");
})
it('TestPixelationTransformation', 9, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new PixelationTransformation();
}
endTime(startTime, 'TestPixelationTransformation');
let pixelation = new PixelationTransformation();
expect(pixelation.getName()).assertContain("PixelationTransformation");
})
it('TestSepiaTransformation', 12, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new SepiaTransformation();
}
endTime(startTime, 'SepiaTransformation');
let speia = new SepiaTransformation();
expect(speia.getName()).assertContain("SepiaTransformation");
})
it('TestSketchTransformation', 13, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new SketchTransformation();
}
endTime(startTime, 'TestSketchTransformation');
let Sketch = new SketchTransformation();
expect(Sketch.getName()).assertContain("SketchTransformation");
})
it('TestMaskTransformation', 14, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new MaskTransformation($r('app.media.icon'));
}
endTime(startTime, 'TestMaskTransformation');
let mask = new MaskTransformation($r('app.media.icon'));
expect(mask.getName()).assertContain("MaskTransformation");
})
it('TestSwirlTransformation', 15, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new SwirlTransformation(10, 180, [10, 10]);
}
endTime(startTime, 'TestSwirlTransformation');
let swirl = new SwirlTransformation(10, 180, [10, 10]);
expect(swirl.getName()).assertContain("SwirlTransformation");
})
it('TestKuwaharaTransformation', 16, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new KuwaharaTransformation(10);
}
endTime(startTime, 'TestKuwaharaTransformation');
let kuwahara = new KuwaharaTransformation(10);
expect(kuwahara.getName()).assertContain("KuwaharaTransformation;radius:10");
})
it('TestToonTransformation', 17, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new ToonTransformation(10);
}
endTime(startTime, 'TestToonTransformation');
let toon = new ToonTransformation(10);
expect(toon.getName()).assertContain("ToonTransformation;threshold:10");
})
it('TestVignetterTransformation', 18, () => {
let startTime = new Date().getTime();
for (let index = 0; index < BASE_COUNT; index++) {
new VignetterTransformation([0.5, 0.5], [0.0, 0.0, 0.0], [0.3, 0.75]);
}
endTime(startTime, 'TestVignetterTransformation');
let vignette = new VignetterTransformation([0.5, 0.5], [0.0, 0.0, 0.0], [0.3, 0.75]);
expect(vignette.getName()).assertContain("VignetterTransformation");
})
})
}
function endTime(startTime: number, tag: string) {
let endTime: number = new Date().getTime();
let averageTime = ((endTime - startTime) * 1000 / BASE_COUNT);
console.info(tag + " startTime: " + endTime);
console.info(tag + " endTime: " + endTime);
console.log(tag + " averageTime: " + averageTime + "μs");
}

View File

@ -24,4 +24,30 @@ export { BlurTransformation } from './src/main/ets/transform/BlurTransformation'
export { SparkMD5 } from "./src/main/ets/3rd_party/sparkmd5/spark-md5"
export { GrayScaleTransformation } from './src/main/ets/transform/GrayScaleTransformation'
export { InvertTransformation } from './src/main/ets/transform/InvertTransformation'
export { ToonTransformation } from './src/main/ets/transform/ToonTransformation'
export { CropCircleTransformation } from './src/main/ets/transform/CropCircleTransformation'
export { CropCircleWithBorderTransformation } from './src/main/ets/transform/CropCircleWithBorderTransformation'
export { KuwaharaTransformation } from './src/main/ets/transform/KuwaharaTransformation'
export { PixelationTransformation } from './src/main/ets/transform/PixelationTransformation'
export { SketchTransformation } from './src/main/ets/transform/SketchTransformation'
export { SwirlTransformation } from './src/main/ets/transform/SwirlTransformation'
export { VignetterTransformation } from './src/main/ets/transform/VignetterTransformation'
export { CropSquareTransformation } from './src/main/ets/transform/CropSquareTransformation'
export { CropTransformation } from './src/main/ets/transform/CropTransformation'
export { MaskTransformation } from './src/main/ets/transform/MaskTransformation'
export { SepiaTransformation } from './src/main/ets/transform/SepiaTransformation'

View File

@ -16,7 +16,7 @@
"type": "module",
"version": "3.0.0-rc.4",
"dependencies": {
"@ohos/gpu_transform": "^1.0.2"
},
"tags": [
"ImageCache",

View File

@ -146,6 +146,8 @@ export class ImageKnifeDispatcher {
src: imageSrc,
headers: currentRequest.imageKnifeOption.headerOption,
allHeaders: currentRequest.headers,
componentWidth:currentRequest.componentWidth,
componentHeight:currentRequest.componentHeight,
customGetImage: currentRequest.imageKnifeOption.customGetImage,
onlyRetrieveFromCache: currentRequest.imageKnifeOption.onlyRetrieveFromCache,
transformation: currentRequest.imageKnifeOption.transformation,
@ -470,7 +472,7 @@ async function requestJob(request: RequestJobRequest): Promise<RequestJobResult
// 图形变化
if (request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined) {
resPixelmap = await request.transformation?.transform(request.context, resPixelmap!, 0, 0)
resPixelmap = await request.transformation?.transform(request.context, resPixelmap!, request.componentWidth, request.componentHeight);
}
LogUtil.log("ImageKnife_DataTime_requestJob_createPixelMap.end:"+request.src)
LogUtil.log("ImageKnife_DataTime_requestJob.end:"+request.src)

View File

@ -71,6 +71,8 @@ export interface RequestJobRequest {
src: string | PixelMap | Resource,
headers?: Array<HeaderOptions>,
allHeaders: Map<string, Object>,
componentWidth: number,
componentHeight: number,
customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise<ArrayBuffer | undefined>,
onlyRetrieveFromCache?: boolean
requestSource: ImageKnifeRequestSource

View File

@ -27,6 +27,10 @@ export class BlurTransformation extends PixelMapTransformation {
this.radius = radius
}
getName(): string {
return this.constructor.name + ';radius:' + this.radius;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let headFilter = effectKit.createEffect(toTransform);
if (headFilter != null) {

View File

@ -27,6 +27,10 @@ export class BrightnessTransformation extends PixelMapTransformation {
this.bright = bright
}
getName(): string {
return this.constructor.name + ';bright:' + this.bright;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let headFilter = effectKit.createEffect(toTransform);
if (headFilter != null) {

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PixelMapTransformation } from './PixelMapTransformation';
import { Size } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
/**
* 图片变换:圆形裁剪效果
*/
@Sendable
export class CropCircleTransformation extends PixelMapTransformation {
private mCenterX: number = 0;
private mCenterY: number = 0;
private mRadius: number = 0;
constructor() {
super();
}
getName(): string {
return this.constructor.name + ';mCenterX:' + this.mCenterX + ';mCenterY:' + this.mCenterY + ';mRadius:' + this.mRadius;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
return await this.transformCircle(toTransform);
}
private async transformCircle(data: PixelMap): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await data.getImageInfo();
let size: Size = {
width: imageInfo.size.width,
height: imageInfo.size.height
};
if (!size) {
console.error("CropCircleTransformation The image size does not exist.");
return data;
}
let height: number = size.height;
let width: number = size.width;
this.mRadius = 0;
if (width > height) {
this.mRadius = height / 2;
} else {
this.mRadius = width / 2;
}
this.mCenterX = width / 2;
this.mCenterY = height / 2;
let bufferData: ArrayBuffer = new ArrayBuffer(data.getPixelBytesNumber());
await data.readPixelsToBuffer(bufferData);
let dataArray = new Uint8Array(bufferData);
for (let h = 0; h <= height; h++) {
for (let w = 0; w <= width; w++) {
if (this.isContainsCircle(w, h)) {
continue;
}
// 针对的点
let index = (h * width + w) * 4;
dataArray[index] = 0;
dataArray[index+1] = 0;
dataArray[index+2] = 0;
dataArray[index+3] = 0;
}
}
await data.writeBufferToPixels(bufferData);
return data;
}
isContainsCircle(x: number, y: number): boolean {
let a = Math.pow((this.mCenterX - x), 2);
let b = Math.pow((this.mCenterY - y), 2);
let c = Math.sqrt((a + b));
return c <= this.mRadius;
}
}

View File

@ -0,0 +1,133 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PixelMapTransformation } from './PixelMapTransformation';
import { Size } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
export interface rgbColor {
r_color: number,
g_color: number,
b_color: number,
}
/**
* 图片变换:圆环裁剪效果
*/
@Sendable
export class CropCircleWithBorderTransformation extends PixelMapTransformation {
private mBorderSize: number = 5;
private mCenterX: number = 0;
private mCenterY: number = 0;
private mRadius: number = 0;
private mRColor: number = 0;
private mGColor: number = 0;
private mBColor: number = 0;
constructor(borderSize: number, value: rgbColor) {
super();
this.mRColor = value.g_color;
this.mGColor = value.g_color;
this.mBColor = value.b_color;
this.mBorderSize = borderSize;
}
getConstructorParams() {
return JSON.stringify([this.mBorderSize, {
r_color: this.mRColor,
g_color: this.mGColor,
b_color: this.mBColor
}]);
}
getName(): string {
return this.constructor.name + ';mBorderSize:' + this.mBorderSize + ';mCenterX:' + this.mCenterX + ';mCenterY:'
+ this.mCenterY + ';mRadius:' + this.mRadius + ';mRColor:' + this.mRColor + ';mGColor:' + this.mGColor + ';mBColor:' + this.mBColor;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
return await this.transformPixelMap(toTransform);
}
private async transformPixelMap(pixelMap: PixelMap): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await pixelMap.getImageInfo();
let size: Size = {
width: imageInfo.size.width,
height: imageInfo.size.height
};
if (!size) {
console.error("CropCircleWithBorderTransformation The image size does not exist.");
return pixelMap;
}
let height: number = size.height;
let width: number = size.width;
this.mRadius = 0;
if (width > height) {
this.mRadius = height / 2;
} else {
this.mRadius = width / 2;
}
this.mCenterX = width / 2;
this.mCenterY = height / 2;
let bufferData = new ArrayBuffer(pixelMap.getPixelBytesNumber());
await pixelMap.readPixelsToBuffer(bufferData);
let dataArray = new Uint8Array(bufferData);
for (let h = 0; h <= height; h++) {
for (let w = 0; w <= width; w++) {
// 不在大圆之内的设置透明
// 在大圆与小圆之间的 设置rgb值
// 小圆之内的不变
let isSmallCircle: boolean = this.isContainsSmallCircle(w, h);
let isBigCircle: boolean = this.isContainsCircle(w, h);
if (isSmallCircle) {
continue;
}
let index = (h * width + w) * 4;
if (!isBigCircle) {
// 设置透明
dataArray[index] = 0;
dataArray[index+1] = 0;
dataArray[index+2] = 0;
dataArray[index+3] = 0;
} else {
// 设置broke
dataArray[index] = this.mRColor;
dataArray[index+1] = this.mGColor;
dataArray[index+2] = this.mBColor;
}
}
}
await pixelMap.writeBufferToPixels(bufferData);
return pixelMap;
}
isContainsCircle(x: number, y: number): boolean {
let a: number = Math.pow((this.mCenterX - x), 2);
let b: number = Math.pow((this.mCenterY - y), 2);
let c: number = Math.sqrt((a + b));
return c <= this.mRadius;
}
isContainsSmallCircle(x: number, y: number): boolean {
let a: number = Math.pow((this.mCenterX - x), 2);
let b: number = Math.pow((this.mCenterY - y), 2);
let c: number = Math.sqrt((a + b));
return c <= (this.mRadius - this.mBorderSize);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PixelMapTransformation } from './PixelMapTransformation';
import { Size } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
/**
* 图片变换:正方形裁剪效果
*/
@Sendable
export class CropSquareTransformation extends PixelMapTransformation {
constructor() {
super();
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await toTransform.getImageInfo();
let size: Size = {
width: imageInfo.size.width,
height: imageInfo.size.height
};
if (!size) {
console.error("CropSquareTransformation The image size does not exist.");
return toTransform;
}
let pixelMapWidth: number = size.width;
let pixelMapHeight: number = size.height;
let targetSize: number = pixelMapWidth > pixelMapHeight ? pixelMapHeight : pixelMapWidth;
let region: image.Region = {
size: { width: targetSize, height: targetSize },
x: pixelMapWidth / 2 - targetSize / 2,
y: pixelMapHeight / 2 - targetSize / 2
};
await toTransform.crop(region);
return toTransform;
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PixelMapTransformation } from './PixelMapTransformation';
import { Size } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
/**
* 图片变换:自定义裁剪效果
*/
@Sendable
export class CropTransformation extends PixelMapTransformation {
private mWidth: number = 0;
private mHeight: number = 0;
private mCropType: number = 0;
constructor(width: number, height: number, cropType: number) {
super();
this.mWidth = width;
this.mHeight = height;
this.mCropType = cropType;
}
getName(): string {
return this.constructor.name + ";mWidth:" + this.mWidth + ";mHeight:" + this.mHeight + ";mCropType:" + this.mCropType;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await toTransform.getImageInfo();
let size: Size = {
width: imageInfo.size.width,
height: imageInfo.size.height
};
if (!size) {
console.error("CropTransformation The image size does not exist.");
return toTransform;
}
let pixelMapWidth: number = size.width;
let pixelMapHeight: number = size.height;
this.mWidth = this.mWidth == 0 ? pixelMapWidth : this.mWidth;
this.mHeight = this.mHeight == 0 ? pixelMapHeight : this.mHeight;
let scaleX: number = this.mWidth / pixelMapWidth;
let scaleY: number = this.mHeight / pixelMapHeight;
let scale: number = Math.max(scaleX, scaleY);
let scaledWidth: number = scale * pixelMapWidth;
let scaledHeight: number = scale * pixelMapHeight;
let left: number = (this.mWidth - scaledWidth) / 2;
let top: number = Math.abs(this.getTop(pixelMapHeight));
let region: image.Region = {
size: {
width: scaledWidth > pixelMapWidth ? pixelMapWidth : scaledWidth,
height: scaledHeight > pixelMapHeight ? pixelMapHeight : scaledHeight
},
x: left < 0 ? 0 : left,
y: top < 0 ? 0 : top
};
toTransform.cropSync(region);
return toTransform;
}
private getTop(scaledHeight: number): number {
switch (this.mCropType) {
case 0:
return 0;
case 1:
return (this.mHeight - scaledHeight) / 2;
case 2:
return this.mHeight - scaledHeight;
default:
return 0;
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PixelMapTransformation } from './PixelMapTransformation';
import { effectKit } from '@kit.ArkGraphics2D';
/**
* 图片变换:灰化效果
*/
@Sendable
export class GrayScaleTransformation extends PixelMapTransformation {
constructor() {
super();
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let headFilter = effectKit.createEffect(toTransform);
if (headFilter != null) {
return await headFilter.grayscale().getEffectPixelMap();
}
return toTransform;
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PixelMapTransformation } from './PixelMapTransformation';
import { effectKit } from '@kit.ArkGraphics2D';
/**
* 图片变换:反转效果
*/
@Sendable
export class InvertTransformation extends PixelMapTransformation {
constructor() {
super();
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let headFilter = effectKit.createEffect(toTransform);
if (headFilter != null) {
return await headFilter.invert().getEffectPixelMap();
}
return toTransform;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GPUImageKuwaharaFilter } from '@ohos/gpu_transform';
import { PixelMapTransformation } from './PixelMapTransformation';
import { image } from '@kit.ImageKit';
/**
* 图片变换:桑原滤波效果
*/
@Sendable
export class KuwaharaTransformation extends PixelMapTransformation {
private radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
getName(): string {
return this.constructor.name + ';radius:' + this.radius;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await toTransform.getImageInfo();
if (!imageInfo.size) {
console.error("KuwaharaTransformation The image size does not exist.");
return toTransform;
}
return await this.kuwaharaGpu(toTransform, imageInfo.size.width, imageInfo.size.height);
}
private async kuwaharaGpu(bitmap: PixelMap, targetWidth: number, targetHeight: number): Promise<PixelMap> {
let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let filter = new GPUImageKuwaharaFilter();
filter.setImageData(bufferData, targetWidth, targetHeight);
filter.setRadius(this.radius);
let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight);
bitmap.writeBufferToPixels(buf);
return bitmap;
}
}

View File

@ -0,0 +1,198 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PixelMapTransformation } from './PixelMapTransformation';
import { Size } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { resourceManager } from '@kit.LocalizationKit';
import { CalculatePixelUtils } from '../utils/CalculatePixelUtils';
import { ColorUtils } from '../utils/ColorUtils';
import { PixelEntry } from './entry/PixelEntry';
/**
* 图片变换:遮罩效果
*/
@Sendable
export class MaskTransformation extends PixelMapTransformation {
private mResourceId: number;
private mResourceModuleName: string;
constructor(resource: Resource) {
super();
this.mResourceId = resource.id;
this.mResourceModuleName = resource.moduleName;
}
getName(): string {
return this.constructor.name + ';resourceId:' + this.mResourceId + ';resourceModuleName:' + this.mResourceModuleName;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo = await toTransform.getImageInfo();
let size: Size = {
width: imageInfo.size.width,
height: imageInfo.size.height
};
if (!size) {
console.error("MaskTransformation The image size does not exist.");
return toTransform;
}
let pixelMapWidth: number = size.width;
let pixelMapHeight: number = size.height;
let targetWidth: number = width;
let targetHeight: number = height;
if (pixelMapWidth > targetWidth && pixelMapHeight > targetHeight) {
let scale = Math.max(targetWidth / pixelMapWidth, targetHeight / pixelMapHeight);
await toTransform.scale(scale, scale);
return await this.openInternal(context, toTransform, scale * pixelMapWidth, scale * pixelMapHeight);
}
return await this.openInternal(context, toTransform, size.width, size.height);
}
private async openInternal(context: Context, bitmap: PixelMap, width: number, height: number): Promise<PixelMap> {
if (context == undefined) {
console.error("MaskTransformation openInternal the context is undefined.");
return bitmap;
}
let moduleContext = context.createModuleContext(this.mResourceModuleName);
if (moduleContext == undefined) {
console.error("MaskTransformation openInternal the moduleContext is undefined.");
return bitmap;
}
let resourceManager = moduleContext.resourceManager as resourceManager.ResourceManager;
if (resourceManager == undefined) {
console.error("MaskTransformation openInternal the resourceManager is undefined.");
return bitmap;
}
let array: Uint8Array = await resourceManager.getMediaContent(this.mResourceId);
let buffer = array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset);
let imageSource: image.ImageSource = image.createImageSource(buffer);
let options: image.DecodingOptions = {
editable: true,
desiredSize: {
width: width,
height: height
}
};
let maskBitmap: PixelMap = await imageSource.createPixelMap(options);
return await this.mask(bitmap, maskBitmap);
}
async mask(bitmap: PixelMap, maskBitmap: PixelMap): Promise<PixelMap> {
let imageInfo = await bitmap.getImageInfo();
let size: Size = {
width: imageInfo.size.width,
height: imageInfo.size.height
};
if (!size) {
console.error("MaskTransformation mask the image size does not exist.");
return bitmap;
}
let width = size.width;
let height = size.height;
let rgbData = CalculatePixelUtils.createInt2DArray(height, width);
let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let dataArray = new Uint8Array(bufferData);
let ph = 0;
let pw = 0;
for (let index = 0; index < dataArray.length; index += 4) {
const r = dataArray[index];
const g = dataArray[index+1];
const b = dataArray[index+2];
const f = dataArray[index+3];
let entry = new PixelEntry();
entry.a = 0;
entry.b = b;
entry.g = g;
entry.r = r;
entry.f = f;
entry.pixel = ColorUtils.rgb(entry.r, entry.g, entry.b);
rgbData[ph][pw] = ColorUtils.rgb(entry.r, entry.g, entry.b);
if (pw == width - 1) {
pw = 0;
ph++;
} else {
pw++;
}
}
let imageInfoMask = await maskBitmap.getImageInfo();
let sizeMask: Size = {
width: imageInfoMask.size.width,
height: imageInfoMask.size.height
};
if (!sizeMask) {
console.error("MaskTransformation mask the sizeMask size does not exist.");
return bitmap;
}
let widthMask = sizeMask.width;
let heightMask = sizeMask.height;
let rgbDataMask = CalculatePixelUtils.createInt2DArray(heightMask, widthMask);
let pixEntry: Array<PixelEntry> = new Array();
let bufferDataM = new ArrayBuffer(maskBitmap.getPixelBytesNumber());
await maskBitmap.readPixelsToBuffer(bufferDataM);
let dataArrayM = new Uint8Array(bufferDataM);
let phM = 0;
let pwM = 0;
for (let index = 0; index < dataArrayM.length; index += 4) {
const r = dataArrayM[index];
const g = dataArrayM[index+1];
const b = dataArrayM[index+2];
const f = dataArrayM[index+3];
let entry = new PixelEntry();
entry.a = 0;
entry.b = b;
entry.g = g;
entry.r = r;
entry.f = f;
entry.pixel = ColorUtils.rgb(entry.r, entry.g, entry.b);
pixEntry.push(entry);
if (entry.r == 0 && entry.g == 0 && entry.b == 0) {
rgbDataMask[phM][pwM] = rgbData[phM][pwM];
} else {
rgbDataMask[phM][pwM] = ColorUtils.rgb(entry.r, entry.g, entry.b);
}
if (pwM == widthMask - 1) {
pwM = 0;
phM++;
} else {
pwM++;
}
}
let bufferNewData = new ArrayBuffer(maskBitmap.getPixelBytesNumber());
let dataNewArray = new Uint8Array(bufferNewData);
let index = 0;
let mh = 0;
let nw = 0;
for (let i = 0; i < dataNewArray.length; i += 4) {
let pixel1 = rgbDataMask[mh][nw];
if (nw == widthMask - 1) {
nw = 0;
mh++;
} else {
nw++;
}
let pR = ColorUtils.red(pixel1);
let pG = ColorUtils.green(pixel1);
let pB = ColorUtils.blue(pixel1);
dataNewArray[i] = pR;
dataNewArray[i+1] = pG;
dataNewArray[i+2] = pB;
dataNewArray[i+3] = pixEntry[index].f;
index++;
}
await maskBitmap.writeBufferToPixels(bufferNewData);
return maskBitmap;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GPUImagePixelationFilter } from '@ohos/gpu_transform';
import { PixelMapTransformation } from './PixelMapTransformation';
import { image } from '@kit.ImageKit';
/**
* Applies a Pixelation effect to the image.
* The pixel with a default of 10.0.
*/
@Sendable
export class PixelationTransformation extends PixelMapTransformation {
private mPixel: number = 10.0;
constructor(pixel?: number) {
super();
if (pixel) {
this.mPixel = pixel;
}
}
getName(): string {
return this.constructor.name + ';pixel:' + this.mPixel;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await toTransform.getImageInfo();
if (!imageInfo.size) {
console.error("PixelationTransformation The image size does not exist.");
return toTransform;
}
return await this.pixelGPU(toTransform, imageInfo.size.width, imageInfo.size.height);
}
private async pixelGPU(bitmap: PixelMap, targetWidth: number, targetHeight: number): Promise<PixelMap> {
let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let filter = new GPUImagePixelationFilter();
filter.setImageData(bufferData, targetWidth, targetHeight);
filter.setPixel(this.mPixel);
let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight);
await bitmap.writeBufferToPixels(buf);
return bitmap;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GPUImageSepiaToneFilter } from '@ohos/gpu_transform';
import { PixelMapTransformation } from './PixelMapTransformation';
import { image } from '@kit.ImageKit';
/**
* 图片变换:乌墨色滤波效果
*/
@Sendable
export class SepiaTransformation extends PixelMapTransformation {
constructor() {
super();
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await toTransform.getImageInfo();
if (!imageInfo.size) {
console.error("SepiaTransformation The image size does not exist.");
return toTransform;
}
return await this.sepiaGPU(toTransform, imageInfo.size.width, imageInfo.size.height);
}
private async sepiaGPU(bitmap: PixelMap, targetWidth: number, targetHeight: number): Promise<PixelMap> {
let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let filter = new GPUImageSepiaToneFilter();
filter.setImageData(bufferData, targetWidth, targetHeight);
let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight);
bitmap.writeBufferToPixels(buf);
return bitmap;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GPUImageSketchFilter } from '@ohos/gpu_transform';
import { PixelMapTransformation } from './PixelMapTransformation';
import { image } from '@kit.ImageKit';
/**
* 图片变换:素描效果
*/
@Sendable
export class SketchTransformation extends PixelMapTransformation {
constructor() {
super();
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await toTransform.getImageInfo();
if (!imageInfo.size) {
console.error("SketchTransformation The image size does not exist.");
return toTransform;
}
return await this.sketchGPU(toTransform, imageInfo.size.width, imageInfo.size.height);
}
private async sketchGPU(bitmap: PixelMap, targetWidth: number, targetHeight: number): Promise<PixelMap> {
let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let filter = new GPUImageSketchFilter();
filter.setImageData(bufferData, targetWidth, targetHeight);
let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight);
bitmap.writeBufferToPixels(buf);
return bitmap;
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GPUImageSwirlFilter } from '@ohos/gpu_transform';
import { PixelMapTransformation } from './PixelMapTransformation';
import { image } from '@kit.ImageKit';
/**
* 图片变换:扭曲效果
*/
@Sendable
export class SwirlTransformation extends PixelMapTransformation {
// degree:值越大范围越大
private mDegree: number = 0;
// 取值范围 0.0 ~ 1.0
private mAngle: number = 0.9;
// 在图中的位置 取值范围 0.0 ~ 1.0
private mXCenter: number = 0.5;
// 在图中的位置 取值范围 0.0 ~ 1.0
private mYCenter: number = 0.5;
constructor(degree: number, angle?: number, centerPoint?: Array<number>) {
super();
this.mDegree = degree;
if (angle) {
this.mAngle = angle;
}
if (centerPoint && centerPoint.length === 2) {
this.mXCenter = centerPoint[0];
this.mYCenter = centerPoint[1];
}
}
getName(): string {
return this.constructor.name + ';degree:' + this.mDegree + ';angle:' + this.mAngle + ';XCenter:' + this.mXCenter
+ ';YCenter:' + this.mYCenter;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await toTransform.getImageInfo();
if (!imageInfo.size) {
console.error("SwirlTransformation The image size does not exist.");
return toTransform;
}
return await this.swirlGPU(toTransform, imageInfo.size.width, imageInfo.size.height);
}
private async swirlGPU(bitmap: image.PixelMap, targetWidth: number, targetHeight: number): Promise<PixelMap> {
let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let filter = new GPUImageSwirlFilter();
filter.setImageData(bufferData, targetWidth, targetHeight);
filter.setRadius(this.mDegree);
filter.setAngle(this.mAngle);
filter.setCenter(this.mXCenter, this.mYCenter);
let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight);
await bitmap.writeBufferToPixels(buf);
return bitmap;
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GPUImageToonFilter } from '@ohos/gpu_transform';
import { PixelMapTransformation } from './PixelMapTransformation';
import { image } from '@kit.ImageKit';
/**
* 图片变换:动画滤镜效果
*/
@Sendable
export class ToonTransformation extends PixelMapTransformation {
private threshold: number = 0.2;
private quantizationLevels: number = 10.0;
constructor(threshold?: number, quantizationLevels?: number) {
super();
if (threshold) {
this.threshold = threshold;
}
if (quantizationLevels) {
this.quantizationLevels = quantizationLevels;
}
}
getName(): string {
return this.constructor.name + ';threshold:' + this.threshold + ';quantizationLevels:' + this.quantizationLevels;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await toTransform.getImageInfo();
if (!imageInfo.size) {
console.error("ToonTransformation The image size does not exist.");
return toTransform;
}
return await this.toonGPU(toTransform, imageInfo.size.width, imageInfo.size.height);
}
private async toonGPU(bitmap: image.PixelMap, targetWidth: number, targetHeight: number) {
let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let filter = new GPUImageToonFilter();
filter.setImageData(bufferData, targetWidth, targetHeight);
filter.setThreshold(this.threshold);
filter.setQuantizationLevels(this.quantizationLevels);
let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight);
bitmap.writeBufferToPixels(buf);
return bitmap;
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GPUImageVignetterFilter } from '@ohos/gpu_transform';
import { PixelMapTransformation } from './PixelMapTransformation';
import { image } from '@kit.ImageKit';
/**
* 图片变换:装饰效果
*/
@Sendable
export class VignetterTransformation extends PixelMapTransformation {
private mXCenter: number = 0.5;
private mYCenter: number = 0.5;
private mRed: number = 0.0;
private mGreen: number = 0.0;
private mBlue: number = 0.0;
private mStart: number = 0.3;
private mEnd: number = 0.75;
constructor(centerPoint: Array<number>, vignetteColor: Array<number>, vignetteSpace: Array<number>) {
super();
if (centerPoint.length === 2) {
this.mXCenter = centerPoint[0];
this.mYCenter = centerPoint[1];
}
if (vignetteColor.length === 3) {
this.mRed = vignetteColor[0];
this.mGreen = vignetteColor[1];
this.mBlue = vignetteColor[2];
}
if (vignetteSpace.length === 2) {
this.mStart = vignetteSpace[0];
this.mEnd = vignetteSpace[1];
}
}
getName(): string {
return this.constructor.name + ';XCenter:' + this.mXCenter + ';YCenter:' + this.mYCenter + ';Red:'
+ this.mRed + ';Green:' + this.mGreen + ';Blue:' + this.mBlue + ';Start:' + this.mStart + ';End:' + this.mEnd;
}
async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise<PixelMap> {
let imageInfo: image.ImageInfo = await toTransform.getImageInfo();
if (!imageInfo.size) {
console.error("VignetterTransformation The image size does not exist.");
return toTransform;
}
return await this.swirlGPU(toTransform, imageInfo.size.width, imageInfo.size.height);
}
private async swirlGPU(bitmap: image.PixelMap, targetWidth: number, targetHeight: number) {
let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let filter = new GPUImageVignetterFilter();
filter.setImageData(bufferData, targetWidth, targetHeight);
filter.setVignetteCenter([this.mXCenter, this.mYCenter]);
filter.setVignetteColor([this.mRed, this.mGreen, this.mBlue]);
filter.setVignetteStart(this.mStart);
filter.setVignetteEnd(this.mEnd);
let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight);
await bitmap.writeBufferToPixels(buf);
return bitmap;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class PixelEntry {
a: number = 0;
b: number = 0;
r: number = 0;
g: number = 0;
f: number = 0;
pixel: number = 0;
public toString(): string {
return "PixelEntry a:" + this.a + ";b:" + this.b + ";r:" + this.r + ";g:" + this.g + ";f:" + this.f;
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export namespace CalculatePixelUtils {
export function createInt2DArray(first_len: number, second_len: number): Array<Array<number>> {
let array = new Array<Array<number>>();
for (let f = 0; f < first_len; f++) {
let s1 = new Array<number>();
for (let s = 0; s < second_len; s++) {
s1.push(0);
}
array.push(s1);
}
return array;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export namespace ColorUtils {
export function red(color: number): number {
return (color >> 16) & 0xFF;
}
export function green(color: number): number {
return (color >> 8) & 0xFF;
}
export function blue(color: number): number {
return color & 0xFF;
}
export function alpha(color: number): number {
return color >>> 24;
}
export function rgb(red: number, green: number, blue: number): number {
return 0xff000000 | (red << 16) | (green << 8) | blue;
}
}

View File

@ -7,8 +7,10 @@
"author": "",
"license": "",
"dependencies": {
"@ohos/gpu_transform": "^1.0.2"
},
"devDependencies": {
"@ohos/hypium": "1.0.16"
}
},
"dynamicDependencies": {}
}

View File

@ -30,4 +30,32 @@ export { BlurTransformation } from '@ohos/imageknife'
export { SparkMD5 } from "@ohos/imageknife"
export { ImageKnifeRequestSource } from "@ohos/imageknife"
export { ImageKnifeRequestSource } from "@ohos/imageknife"
export { CropCircleTransformation } from '@ohos/imageknife'
export { CropCircleWithBorderTransformation } from '@ohos/imageknife'
export { CropSquareTransformation } from '@ohos/imageknife'
export { CropTransformation } from '@ohos/imageknife'
export { GrayScaleTransformation } from '@ohos/imageknife'
export { InvertTransformation } from '@ohos/imageknife'
export { KuwaharaTransformation } from '@ohos/imageknife'
export { MaskTransformation } from '@ohos/imageknife'
export { PixelationTransformation } from '@ohos/imageknife'
export { SepiaTransformation } from '@ohos/imageknife'
export { SketchTransformation } from '@ohos/imageknife'
export { SwirlTransformation } from '@ohos/imageknife'
export { ToonTransformation } from '@ohos/imageknife'
export { VignetterTransformation } from '@ohos/imageknife'

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@Entry
@Component
export struct IndexPage {
build() {
}
}

View File

@ -1,5 +1,5 @@
{
"src": [
"pages/Index"
"pages/IndexPage"
]
}
}