/*
* Copyright (c) 2024-2025 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 imageEffect from 'libentry.so';
import image from '@ohos.multimedia.image';
import { ImageUtils } from '../utils/ImageUtils';
import { fileUri } from '@kit.CoreFileKit';
import fs from '@ohos.file.fs';
interface FilterDataType {
brightnessSetValue: number,
brightnessSelect: boolean,
contrastSetValue: number,
contrastSelect: boolean,
cropSetValue: number,
cropSelect: boolean,
customSetValue: number,
customSelect: boolean
}
const DEFAULT_FILTER_DATA: FilterDataType = {
brightnessSetValue: 100,
brightnessSelect: true,
contrastSetValue: 0,
contrastSelect: false,
cropSetValue: 100,
cropSelect: false,
customSetValue: 0,
customSelect: false
};
@Entry
@Component
struct ImageEditPage {
private tag: string = '[Sample_ImageEdit]';
settingBtn: Resource = $r('app.media.ic_public_settings');
@Provide pixelMap: image.PixelMap | undefined = undefined;
@Provide displayPixelMap: image.PixelMap | undefined = undefined;
@State filterOptions: Array<Array<string | number>> = [];
@State filterInfo: string = "";
@State nowSelectedData: FilterDataType = DEFAULT_FILTER_DATA;
aboutToAppear(): void {
console.info(`${this.tag} aboutToAppear called`);
ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920')).then(pixelMap => {
this.pixelMap = pixelMap;
})
ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920')).then(pixelMap => {
this.displayPixelMap = pixelMap;
})
console.info(`${this.tag} aboutToAppear done`);
}
aboutToDisappear(): void {
console.info(`${this.tag} aboutToDisappear called`);
console.info(`${this.tag} aboutToDisappear done`);
}
build() {
Column() {
Row() {
Row() {
Text($r('app.string.image_edit'))
.fontColor(Color.White)
.fontSize('app.float.title_font_size')
.margin({ left: $r('app.float.home_page_title_margin') })
}
Blank()
Row({ space: 0 }) {
Button() {
Image(this.settingBtn)
.width($r('app.float.title_image_width'))
.height($r('app.float.title_image_height'))
.id('btn_setting')
}
.height('100%')
.type(ButtonType.Normal)
.aspectRatio(1)
.backgroundColor(Color.Transparent)
.onClick(() => {
if (this.dialogController != undefined) {
console.info(`${this.tag} to open setting dialog`);
this.dialogController.open();
}
})
}
}
.width('100%')
.height($r('app.float.home_page_title_height'))
.margin({ top: $r('app.float.home_page_title_margin') })
Column() {
Image(this.displayPixelMap)
.objectFit(ImageFit.None)
}
.clip(true)
.width('100%')
.height('85%')
Column() {
Row() {
Button("Reset").id("btn_reset").onClick(() => {
this.resetParams();
this.pixelInit();
}).width(100).margin({ left: 3, right: 3, top: 3, bottom: 6 })
Button("Apply").id("btn_apply").onClick(() => {
this.doApply();
}).width(100).margin({ left: 3, right: 3, top: 3, bottom: 6 })
}
.justifyContent(FlexAlign.Center)
}
.align(Alignment.End)
.width('100%')
.height('6%')
.margin({ top: $r('app.float.home_page_title_margin') })
.backgroundColor(Color.Black)
}
.width('100%')
.height('100%')
.backgroundColor(Color.Black)
}
private async doSavePixel(): Promise<void> {
let pixelMap = await ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920'));
const imagePackerApi = image.createImagePacker();
const packOption: image.PackingOption = {
format: 'image/jpeg',
quality: 100
};
let filePath = getContext().filesDir + "/test.jpg";
let uri = fileUri.getUriFromPath(filePath);
let imageData = await imagePackerApi.packing(pixelMap, packOption);
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
let writeLen = fs.writeSync(file.fd, imageData);
fs.closeSync(file);
console.info(`write data to file succeed and size is ${writeLen}`);
}
private confirmInfo() {
this.filterOptions = [];
if (this.nowSelectedData.brightnessSelect) {
let brightnessArray: (string | number)[] = [];
brightnessArray.push("Brightness", this.nowSelectedData.brightnessSetValue);
this.filterOptions.push(brightnessArray);
}
if (this.nowSelectedData.contrastSelect) {
let contrastArray: (string | number)[] = [];
contrastArray.push("Contrast", this.nowSelectedData.contrastSetValue);
this.filterOptions.push(contrastArray);
}
if (this.nowSelectedData.cropSelect) {
let cropArray: (string | number)[] = [];
cropArray.push("Crop", this.nowSelectedData.cropSetValue);
this.filterOptions.push(cropArray);
}
if (this.nowSelectedData.customSelect) {
let customArray: (string | number)[] = [];
customArray.push("CustomBrightness", this.nowSelectedData.customSetValue);
this.filterOptions.push(customArray);
}
}
private async doApply(): Promise<void> {
this.confirmInfo();
if (this.nowSelectedData.brightnessSelect || this.nowSelectedData.contrastSelect ||
this.nowSelectedData.cropSelect || this.nowSelectedData.customSelect) {
await this.doSavePixel();
let filePath = getContext().filesDir + "/test.jpg";
imageEffect.apply(filePath, [...this.filterOptions]);
this.displayPixelMap = await ImageUtils.getInstance().getPixelMapByFilePath(filePath);
}
}
resetParams() {
this.nowSelectedData = DEFAULT_FILTER_DATA;
}
private async pixelInit(): Promise<void> {
this.displayPixelMap?.release();
this.displayPixelMap = await ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920'))
}
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample({
cancel: this.onCancel,
confirm: this.onAccept,
filterOptions: $filterOptions,
filterInfo: $filterInfo,
nowSelectedData: $nowSelectedData
}),
cancel: this.existApp,
autoCancel: true,
})
onCancel() {
console.info(`Callback when the cancel button is clicked`);
}
onAccept(nowSelectedData: FilterDataType) {
console.info(`Callback when the confirm button is clicked`);
this.nowSelectedData = nowSelectedData;
}
existApp() {
console.info(`click the callback in the blank area`);
}
}
@CustomDialog
struct CustomDialogExample {
@State brightnessSetValue: number = 100;
@State brightnessSelect: boolean = true;
@State contrastSetValue: number = 0;
@State contrastSelect: boolean = false;
@State cropSetValue: number = 100;
@State cropSelect: boolean = false;
@State customSetValue: number = 0;
@State customSelect: boolean = false;
@Link filterOptions: Array<Array<string | number>>;
@Link filterInfo: string;
@Link nowSelectedData: FilterDataType;
controller: CustomDialogController;
cancel: () => void = () => {
};
confirm: (nowSelectedData: FilterDataType) => void = (nowSelectedData: FilterDataType) => {
};
@State formatList: Array<String> = ["Format:default", "Format:rgba_8888", "Format:nv21", "Format:nv12"];
@State handlePopup: boolean = false;
aboutToAppear(): void {
this.brightnessSetValue = this.nowSelectedData.brightnessSetValue;
this.brightnessSelect = this.nowSelectedData.brightnessSelect;
this.contrastSetValue = this.nowSelectedData.contrastSetValue;
this.contrastSelect = this.nowSelectedData.contrastSelect;
this.cropSetValue = this.nowSelectedData.cropSetValue;
this.cropSelect = this.nowSelectedData.cropSelect;
this.customSetValue = this.nowSelectedData.customSetValue;
this.customSelect = this.nowSelectedData.customSelect;
}
@Builder
FilterInfoMenu() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Center }) {
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start }) {
Text(this.filterInfo).fontSize(16)
}
}
.width('60%').height('15%')
.opacity(0.8)
.id("filter_info_menu")
}
private async doLookFilterInfo(name: String): Promise<void> {
this.filterInfo = imageEffect.lookupFilterInfo(name);
}
build() {
Column() {
Column() {
Divider().height(2).color(0xcccccc)
Column() {
Text('Filter')
.width('100%')
.fontSize(18)
.margin({ bottom: 10 })
Row() {
Column() {
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'brightnessCheckbox', group: 'filterCheckboxGroup' })
.selectedColor(0x39a2db)
.select(this.brightnessSelect)
.onChange((value: boolean) => {
this.brightnessSelect = value;
})
.width(10)
.height(14)
.id("checkbox_brightness")
Text('Brightness').fontSize(10).width('18%')
Slider({
value: this.brightnessSetValue,
min: -100,
max: 100,
style: SliderStyle.OutSet
})
.showTips(true, this.brightnessSetValue.toFixed())
.onChange((value: number, mode: SliderChangeMode) => {
this.brightnessSetValue = value
console.info('value:' + value + 'mode:' + mode.toString())
})
.width('60%')
.id("slider_brightness")
// toFixed(0)将滑动条返回值处理为整数精度
Column() {
Text(this.brightnessSetValue.toFixed(0)).fontSize(12)
}.width('8%')
Column() {
Image($r('app.media.ic_public_search'))
.width('5%')
.height('3.7%')
}.bindMenu(this.FilterInfoMenu, {
onAppear: () => {
this.doLookFilterInfo("Brightness");
},
onDisappear: () => {
this.filterInfo = "";
}
})
.margin({ left: 2 })
.id("btn_search_brightness")
}
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'contrastCheckbox', group: 'filterCheckboxGroup' })
.selectedColor(0x39a2db)
.select(this.contrastSelect)
.onChange((value: boolean) => {
this.contrastSelect = value;
})
.width(10)
.height(14)
.id("checkbox_contrast")
Text('Contrast').fontSize(10).width('18%')
Slider({
value: this.contrastSetValue,
min: -100,
max: 100,
style: SliderStyle.OutSet
})
.showTips(true, this.contrastSetValue.toFixed())
.onChange((value: number, mode: SliderChangeMode) => {
this.contrastSetValue = value
console.info('value:' + value + 'mode:' + mode.toString())
})
.width('60%')
.id("slider_contrast")
// toFixed(0)将滑动条返回值处理为整数精度
Column() {
Text(this.contrastSetValue.toFixed(0)).fontSize(12)
}.width('8%')
Column() {
Image($r('app.media.ic_public_search'))
.width('5%')
.height('3.7%')
}.bindMenu(this.FilterInfoMenu, {
onAppear: () => {
this.doLookFilterInfo("Contrast");
},
onDisappear: () => {
this.filterInfo = "";
}
})
.margin({ left: 2 })
.id("btn_search_contrast")
}
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'cropCheckbox', group: 'filterCheckboxGroup' })
.selectedColor(0x39a2db)
.select(this.cropSelect)
.onChange((value: boolean) => {
this.cropSelect = value;
})
.width(10)
.height(14)
.id("checkbox_crop")
Text('Crop').fontSize(10).width('18%')
Slider({
value: this.cropSetValue,
min: 1,
max: 100,
style: SliderStyle.OutSet
})
.showTips(true, this.cropSetValue.toFixed())
.onChange((value: number, mode: SliderChangeMode) => {
this.cropSetValue = value
console.info('value:' + value + 'mode:' + mode.toString())
})
.width('60%')
.id("slider_crop")
// toFixed(0)将滑动条返回值处理为整数精度
Column() {
Text(this.cropSetValue.toFixed(0)).fontSize(12)
}.width('8%')
Column() {
Image($r('app.media.ic_public_search'))
.width('5%')
.height('3.7%')
}.bindMenu(this.FilterInfoMenu, {
onAppear: () => {
this.doLookFilterInfo("Crop");
},
onDisappear: () => {
this.filterInfo = "";
}
})
.margin({ left: 2 })
.id("btn_search_crop")
}
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Checkbox({ name: 'customCheckbox', group: 'filterCheckboxGroup' })
.selectedColor(0x39a2db)
.select(this.customSelect)
.onChange((value: boolean) => {
this.customSelect = value;
})
.width(10)
.height(14)
.id("checkbox_custom")
Text('Custom').fontSize(10).width('18%')
Slider({
value: this.customSetValue,
min: -100,
max: 100,
style: SliderStyle.OutSet
})
.showTips(true, this.customSetValue.toFixed())
.onChange((value: number, mode: SliderChangeMode) => {
this.customSetValue = value
console.info('value:' + value + 'mode:' + mode.toString())
})
.width('60%')
.id("slider_custom")
// toFixed(0)将滑动条返回值处理为整数精度
Column() {
Text(this.customSetValue.toFixed(0)).fontSize(12)
}.width('8%')
Column() {
Image($r('app.media.ic_public_search'))
.width('5%')
.height('3.7%')
}.bindMenu(this.FilterInfoMenu, {
onAppear: () => {
this.doLookFilterInfo("CustomBrightness");
},
onDisappear: () => {
this.filterInfo = "";
}
})
.margin({ left: 2 })
.id("btn_search_custom")
}
}
.width('100%')
}
}
}.margin({ bottom: 10 })
Column() {
Divider().height(2).color(0xCCCCCC);
Column() {
Text($r('app.string.look_up'))
.width('100%')
.fontSize(18)
.margin({ bottom: 10 })
Row() {
Column() {
Text($r('app.string.btn_search'))
.width('15%')
.fontSize(20)
.margin({ left: '35%' })
}
Column() {
Image($r('app.media.ic_public_arrow_right'))
.fillColor(Color.Black)
.width('10%')
.height('4%')
}
}
.id("btn_search")
.width('100%')
.justifyContent(FlexAlign.Start)
.bindMenu(this.LookupCategoryMenuBuilder)
}
}.margin({ bottom: 10 })
Divider().height(2).color(0xCCCCCC)
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button($r('app.string.btn_cancel'))
.onClick(() => {
this.controller.close()
this.cancel();
}).backgroundColor(0xffffff)
.fontColor(Color.Black)
.id("btn_dialog_cancel")
Button($r('app.string.btn_confirm'))
.onClick(() => {
this.controller.close();
this.confirm({
brightnessSetValue: this.brightnessSetValue,
brightnessSelect: this.brightnessSelect,
contrastSetValue: this.contrastSetValue,
contrastSelect: this.contrastSelect,
cropSetValue: this.cropSetValue,
cropSelect: this.cropSelect,
customSetValue: this.customSetValue,
customSelect: this.customSelect
});
}).backgroundColor(0xffffff)
.fontColor(Color.Red)
.id("btn_dialog_confirm")
}
}.margin(24)
}
@Builder
LookupCategoryMenuBuilder() {
Menu() {
MenuItem({
content: "Format",
builder: (): void => this.FormatMenuBuilder()
})
}.id("menu_category")
}
@Builder
FormatMenuBuilder() {
Menu() {
ForEach(this.formatList, (item: string, index) => {
LookupFilterMenuItem({ item: item })
.id("menu_format")
})
}
}
}
@Component
struct LookupFilterMenuItem {
@State item: string = "";
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogDetails({
item: $item
}),
offset: { dx: 0, dy: 220 },
autoCancel: true,
})
build() {
MenuItem({ content: this.item })
.onClick(() => {
if (this.dialogController != null) {
this.dialogController.open()
}
})
}
}
@CustomDialog
struct CustomDialogDetails {
@Link item: string
controller?: CustomDialogController
build() {
Column() {
Text('Filters:\n' + imageEffect.lookupFilters(this.item))
.fontSize(16)
}
}
}