diff --git a/ohos/entry/src/main/ets/pages/Index.ets b/ohos/entry/src/main/ets/pages/Index.ets index bc0d1733eb..a72432b9eb 100755 --- a/ohos/entry/src/main/ets/pages/Index.ets +++ b/ohos/entry/src/main/ets/pages/Index.ets @@ -21,67 +21,80 @@ import * as pag from 'libpag'; @Entry @Component struct Index { - @State message: string = ""; - @State @Watch("updateMessage") stateString: string = ""; - @State @Watch("updateMessage") progress: number = 0; + @State imageViewControllers: Array = new Array(); @State viewController: pag.PAGViewController = new pag.PAGViewController(); + @State pageIndex: number = 0; aboutToAppear(): void { let manager = getContext(this).resourceManager; let file = pag.PAGFile.LoadFromAssets(manager, "PAG_LOGO.pag"); this.viewController.setComposition(file); - this.viewController.setRepeatCount(1); - this.viewController.addListener(new WeakRef(this)); + this.viewController.setRepeatCount(-1); this.viewController.play(); - } - - onAnimationStart = (viewController: pag.PAGViewController) => { - this.stateString = viewController.uniqueID() + ` PAG start`; - } - onAnimationEnd = (viewController: pag.PAGViewController) => { - this.stateString = viewController.uniqueID() + ` PAG end`; - } - onAnimationRepeat = (viewController: pag.PAGViewController) => { - this.stateString = viewController.uniqueID() + ` PAG repeat`; - } - onAnimationCancel = (viewController: pag.PAGViewController) => { - this.stateString = viewController.uniqueID() + ` PAG cancel`; - } - onAnimationUpdate = (viewController: pag.PAGViewController) => { - this.progress = viewController.getProgress(); - } - updateMessage() { - this.message = this.stateString + ` progress ${this.progress.toFixed(2)}`; + for (let index = 0; index < 20; index++) { + let imageViewController = new pag.PAGImageViewController(); + let file = pag.PAGFile.LoadFromAssets(manager, "list/" + index + ".pag"); + imageViewController.setComposition(file); + imageViewController.setRepeatCount(-1); + this.imageViewControllers.push(imageViewController); + } } build() { - Row() { - Column() { - pag.PAGView({ - controller: this.viewController - }) - .height('50%') - .onClick(() => { + Column() { + Row() { + if (this.pageIndex == 1) { + Grid() { + ForEach(this.imageViewControllers, (item: pag.PAGImageViewController) => { + GridItem() { + pag.PAGImageView({ + controller: item + }) + .onClick(() => { + if (item.isPlaying()) { + item.pause(); + } else { + item.play(); + } + }) + }.width("25%").height(100) + }) + }.width("100%") + } else { + pag.PAGView({ + controller: this.viewController + }).onClick(() => { if (this.viewController.isPlaying()) { this.viewController.pause(); } else { this.viewController.play(); } }) + } + }.height("70%") + + + Row() { + Button("PAGView").width("40%").margin(10) + .onClick(() => { + this.pageIndex = 0; + this.viewController.play(); + this.imageViewControllers.forEach((item: pag.PAGImageViewController) => { + item.pause(); + }) + }) - Text(this.message) - .fontSize(50) - .fontWeight(FontWeight.Bold) + Button("PAGImageView").width("40%") .onClick(() => { - this.viewController.setProgress(0.5); - this.viewController.setRepeatCount(0); + this.pageIndex = 1; + this.imageViewControllers.forEach((item: pag.PAGImageViewController) => { + item.play(); + }) + this.viewController.pause(); }) - .height('50%') } - .width('100%') } - .height('100%') } } diff --git a/ohos/entry/src/main/resources/rawfile/list/0.pag b/ohos/entry/src/main/resources/rawfile/list/0.pag new file mode 100644 index 0000000000..51d4d6ee15 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/0.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69fc9a0e735aba25af7d7b8920e62dad89d54528f273ae31e0e7380327e379a9 +size 2362 diff --git a/ohos/entry/src/main/resources/rawfile/list/1.pag b/ohos/entry/src/main/resources/rawfile/list/1.pag new file mode 100644 index 0000000000..f0e52fa575 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/1.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:806d8f95e636d9f3c7ae59cdb89964d9d0106f84d5ee98e8905ec4c62b19bda2 +size 2085 diff --git a/ohos/entry/src/main/resources/rawfile/list/10.pag b/ohos/entry/src/main/resources/rawfile/list/10.pag new file mode 100644 index 0000000000..f3fb892012 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/10.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72e28eb5af6e730a92f68ee0e44750ec65342c9d4800d1662df85d4da597869f +size 4041 diff --git a/ohos/entry/src/main/resources/rawfile/list/11.pag b/ohos/entry/src/main/resources/rawfile/list/11.pag new file mode 100644 index 0000000000..65c17718ef --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/11.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4f48e423f6f8ae395edc927a524b12c15a2332196d9704d17b0ada83cdb4d9a +size 39351 diff --git a/ohos/entry/src/main/resources/rawfile/list/12.pag b/ohos/entry/src/main/resources/rawfile/list/12.pag new file mode 100644 index 0000000000..2184902167 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/12.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:647e89768a65b3dd6627c9d75394fd5cd04ad65dba4f094aa97f6c0ce6ae19b2 +size 53184 diff --git a/ohos/entry/src/main/resources/rawfile/list/13.pag b/ohos/entry/src/main/resources/rawfile/list/13.pag new file mode 100644 index 0000000000..9e59831e11 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/13.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc8d08c059c9afb35060e0a90f90cbe3f3c76acc299b7a4bab8d1d9cdda25924 +size 12093 diff --git a/ohos/entry/src/main/resources/rawfile/list/14.pag b/ohos/entry/src/main/resources/rawfile/list/14.pag new file mode 100644 index 0000000000..f732212074 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/14.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73f323f3bbe5476a53d974812bb9423d2851531fd76d68ce0500e684460ef2cb +size 60385 diff --git a/ohos/entry/src/main/resources/rawfile/list/15.pag b/ohos/entry/src/main/resources/rawfile/list/15.pag new file mode 100644 index 0000000000..fae5596825 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/15.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d21f7ab9025ffdece68a3d5f39823b9c891f9ffeaa99f305d9e47e5f3324a41 +size 18686 diff --git a/ohos/entry/src/main/resources/rawfile/list/16.pag b/ohos/entry/src/main/resources/rawfile/list/16.pag new file mode 100644 index 0000000000..11d25a0ec6 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/16.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0b67ce5052ae6a602ac57285f6c560b2019bc33d3217971236b9b489f3b8c1f +size 83559 diff --git a/ohos/entry/src/main/resources/rawfile/list/17.pag b/ohos/entry/src/main/resources/rawfile/list/17.pag new file mode 100644 index 0000000000..6059e21122 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/17.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a48f6a404aabdf48e658da18204a529c29a51e0294d785bd8304d9753d3b3198 +size 250453 diff --git a/ohos/entry/src/main/resources/rawfile/list/18.pag b/ohos/entry/src/main/resources/rawfile/list/18.pag new file mode 100644 index 0000000000..7271f7bf38 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/18.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70fb088a60593d357c6ecb1eb56e4469241b4086822a0641908207f09587567b +size 25117 diff --git a/ohos/entry/src/main/resources/rawfile/list/19.pag b/ohos/entry/src/main/resources/rawfile/list/19.pag new file mode 100644 index 0000000000..2184902167 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/19.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:647e89768a65b3dd6627c9d75394fd5cd04ad65dba4f094aa97f6c0ce6ae19b2 +size 53184 diff --git a/ohos/entry/src/main/resources/rawfile/list/2.pag b/ohos/entry/src/main/resources/rawfile/list/2.pag new file mode 100644 index 0000000000..930d992de5 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/2.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86262b2775863b102d6679ca4a1e1f54479c5b5a7fac2b6e4a450d25d6b1a0ed +size 2366 diff --git a/ohos/entry/src/main/resources/rawfile/list/3.pag b/ohos/entry/src/main/resources/rawfile/list/3.pag new file mode 100644 index 0000000000..1a399a6e56 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/3.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:904632673e99e9bff11f886638b46b6ed4c591e29d1e3c6113448391957afd18 +size 8669 diff --git a/ohos/entry/src/main/resources/rawfile/list/4.pag b/ohos/entry/src/main/resources/rawfile/list/4.pag new file mode 100644 index 0000000000..6544589e77 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/4.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c7afab1c5070241dfd0ae8aa3906fd3982ce7ef242afc6ed3bba89af34b470a +size 39081 diff --git a/ohos/entry/src/main/resources/rawfile/list/5.pag b/ohos/entry/src/main/resources/rawfile/list/5.pag new file mode 100644 index 0000000000..d83923938e --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/5.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53965e509439dcf98d92fbb94481d2ffa699dae2083adcdfa944df5f267c15e4 +size 24008 diff --git a/ohos/entry/src/main/resources/rawfile/list/6.pag b/ohos/entry/src/main/resources/rawfile/list/6.pag new file mode 100644 index 0000000000..8438e2f701 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/6.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:222490f1895749b420cb7551ecb5d3b6426c763e08eb5fd0443d18e17346e4d5 +size 14700 diff --git a/ohos/entry/src/main/resources/rawfile/list/7.pag b/ohos/entry/src/main/resources/rawfile/list/7.pag new file mode 100644 index 0000000000..6fa175c27f --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/7.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dfb8690509d72dc503af2b123f2b1d969843b2efe03bd851a20cb16617c4ef79 +size 5745 diff --git a/ohos/entry/src/main/resources/rawfile/list/8.pag b/ohos/entry/src/main/resources/rawfile/list/8.pag new file mode 100644 index 0000000000..7c71a57618 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/8.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77e9e5ab022b46dd9dd9e51553a410ce3dd33ef78e909a7515c861baf3a53182 +size 10218 diff --git a/ohos/entry/src/main/resources/rawfile/list/9.pag b/ohos/entry/src/main/resources/rawfile/list/9.pag new file mode 100644 index 0000000000..8ef3ae0081 --- /dev/null +++ b/ohos/entry/src/main/resources/rawfile/list/9.pag @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45ba151ecb5f8ccdb1d01501358e782cc2ad631abb3eb705fd17d93ebb8ac767 +size 35537 diff --git a/ohos/libpag/Index.ets b/ohos/libpag/Index.ets index c62daca4d0..e29b386196 100644 --- a/ohos/libpag/Index.ets +++ b/ohos/libpag/Index.ets @@ -45,3 +45,7 @@ export { PAGTextLayer } from './src/main/ets/PAGTextLayer' export { PAGVideoRange } from './src/main/ets/PAGVideoRange' export { PAG } from './src/main/ets/PAG' + +export { PAGDiskCache } from './src/main/ets/PAGDiskCache' + +export { PAGImageView, PAGImageViewController, PAGImageViewListener } from './src/main/ets/PAGImageView' \ No newline at end of file diff --git a/ohos/libpag/src/main/cpp/types/libpag/Index.d.ts b/ohos/libpag/src/main/cpp/types/libpag/Index.d.ts index b8ee7d51e6..d1029ebb81 100644 --- a/ohos/libpag/src/main/cpp/types/libpag/Index.d.ts +++ b/ohos/libpag/src/main/cpp/types/libpag/Index.d.ts @@ -299,6 +299,8 @@ export declare class JPAGSurface { updateSize(): void; makeSnapshot(): image.PixelMap | null; + + updateSize(): void; } export declare class JPAGView { @@ -451,4 +453,54 @@ export declare class JPAGDiskCache { static ReadFile(key: string): ArrayBuffer; static WriteFile(key: string, data: ArrayBuffer): boolean; +} + +export declare class JPAGImageView { + flush(): boolean; + + setComposition(composition: JPAGComposition | null, maxFrameRate: number): void; + + scaleMode(): number; + + setScaleMode(scaleMode: number); + + matrix(): Array; + + setMatrix(matrix: Array); + + cacheAllFramesInMemory(): boolean; + + setCacheAllFramesInMemory(enable: boolean); + + repeatCount(): number; + + setRepeatCount(repeatCount: number): void; + + play(): void; + + isPlaying(): boolean; + + pause(): void; + + setStateChangeCallback(callback: (number) => void): void; + + setProgressUpdateCallback(callback: () => void): void; + + uniqueID(): string; + + setRenderScale(renderScale: number): void; + + renderScale(): number; + + currentFrame(): number; + + setCurrentFrame(progress: number): void; + + numFrames(): number; + + currentImage(): image.PixelMap | null; + + update(): void; + + release(); } \ No newline at end of file diff --git a/ohos/libpag/src/main/ets/PAGImageView.ets b/ohos/libpag/src/main/ets/PAGImageView.ets new file mode 100644 index 0000000000..5420f23981 --- /dev/null +++ b/ohos/libpag/src/main/ets/PAGImageView.ets @@ -0,0 +1,379 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// 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 { Matrix4 } from '@ohos.arkui.node'; +import { JPAGImageView } from 'libpag.so' +import { PAGComposition } from './PAGComposition'; +import { PAGScaleMode } from './PAGScaleMode'; +import { PAGFile } from './PAGFile'; +import { PAGAnimatorState } from './private/PAGAnimatorState'; +import { PAGUtils } from './private/PAGUtils'; + +export interface PAGImageViewListener { + /** + * Notifies the beginning of the animation. It can be called from either the UI thread or the thread + * that calls the play method. + */ + onAnimationStart?: (view: PAGImageViewController) => void; + + /** + * Notifies the end of the animation. It can only be called from the UI thread. + */ + onAnimationEnd?: (view: PAGImageViewController) => void; + + /** + * Notifies the repetition of the animation. It can only be called from the UI thread. + */ + onAnimationRepeat?: (view: PAGImageViewController) => void; + + /** + * Notifies the cancellation of the animation. It can be called from either the UI thread or the + * thread that calls the stop method. + */ + onAnimationCancel?: (view: PAGImageViewController) => void; + + /** + * Notifies another frame of the animation has occurred. It may be called from an arbitrary + * thread if the animation is running asynchronously. + */ + onAnimationUpdate?: (view: PAGImageViewController) => void; +} + +@Observed +export class PAGImageViewController { + /** + * Returns the current PAGComposition in the PAGImageView. Returns null if the internal + * composition was loaded from a pag file path. + */ + getComposition(): PAGComposition | null { + return this.filePath != null ? null : this.composition; + } + + /** + * Sets a new PAGComposition and the maxFrameRate limit to the PAGImageView. Note: If the + * composition is already added to another PAGImageView, it will be removed from the previous + * PAGImageView. + */ + setComposition(composition: PAGComposition | null, maxFrameRate: number = 30.0) { + this.filePath = null; + this.composition = composition; + this.jImageView.setComposition(composition?.getNativeComposition(), maxFrameRate); + } + + /** + * The path string of a pag file set by setPath() or setPathAsync(). + * @returns + */ + getPath(): string | null { + return this.filePath; + } + + /** + * Loads a pag file from the local path, returns false if the file does not exist or the data is not a pag file. + * @param path + * @returns + */ + setPath(path: string, maxFrameRate: number = 30.0): boolean { + this.filePath = path; + let file = PAGFile.LoadFromPath(path); + this.composition = file; + this.jImageView.setComposition(file?.getNativeComposition(), maxFrameRate); + return file != null; + } + + /** + * Asynchronously load a pag file from the specific path which can be a network path or a local path. + * @param path + * @returns + */ + setPathAsync(path: string, maxFrameRate: number = 30.0): Promise { + this.filePath = path; + return new Promise((resolve) => { + PAGFile.LoadFromPathAsync(path).then((pagFile) => { + this.composition = pagFile; + this.jImageView.setComposition(pagFile?.getNativeComposition(), maxFrameRate); + resolve(pagFile) + }) + }) + } + + /** + * Returns the current scale mode. + */ + public scaleMode(): PAGScaleMode { + return this.jImageView.scaleMode(); + } + + /** + * Specifies the rule of how to scale the pag content to fit the PAGImageView's size. The + * current matrix of the PAGImageView changes when this method is called. + */ + setScaleMode(scaleMode: PAGScaleMode) { + this.jImageView.setScaleMode(scaleMode); + } + + /** + * Returns a copy of the current matrix. + */ + matrix(): Matrix4 { + return PAGUtils.ToTsMatrix(this.jImageView.matrix()); + } + + /** + * Sets the transformation which will be applied to the composition. The scaleMode property + * will be set to PAGScaleMode::None when this method is called. + */ + setMatrix(matrix: Matrix4) { + this.jImageView.setMatrix(PAGUtils.ToNativeMatrix(matrix)); + } + + /** + * This value defines the scale factor for the size of the cached image frames, which ranges + * from 0.0 to 1.0. A scale factor less than 1.0 may result in blurred output, but it can reduce + * graphics memory usage, increasing the rendering performance. The default value is 1.0. + */ + renderScale(): number { + return this.jImageView.renderScale(); + } + + /** + * Sets the value of the renderScale property. + */ + setRenderScale(renderScale: number) { + this.jImageView.setRenderScale(renderScale); + } + + /** + * If set to true, the PAGImageView loads all image frames into the memory, which will + * significantly increase the rendering performance but may cost lots of additional memory. Set + * it to true if you prefer rendering speed over memory usage. If set to false, the PAGImageView + * loads only one image frame at a time into the memory. The default value is false. + */ + cacheAllFramesInMemory(): boolean { + return this.jImageView.cacheAllFramesInMemory(); + } + + /** + * Sets the value of the cacheAllFramesInMemory property. + */ + setCacheAllFramesInMemory(enable: boolean) { + this.jImageView.setCacheAllFramesInMemory(enable); + } + + /** + * Returns the current frame index the PAGImageView is rendering. + */ + currentFrame(): number { + return this.jImageView.currentFrame(); + } + + /** + * Sets the frame index for the PAGImageView to render. + */ + setCurrentFrame(currentFrame: number) { + this.jImageView.setCurrentFrame(currentFrame); + } + + /** + * Returns a PixelMap capturing the contents of the current PAGImageView. + */ + currentImage(): PixelMap | null { + return this.jImageView.currentImage(); + } + + /** + * Starts to play the animation from the current position. Calling the play() method when the + * animation is already playing has no effect. The play() method does not alter the animation's + * current position. However, if the animation previously reached its end, it will restart from + * the beginning. + */ + play() { + this.jImageView.play(); + } + + /** + * Indicates whether the animation is playing. + */ + isPlaying(): boolean { + return this.jImageView.isPlaying(); + } + + /** + * Cancels the animation at the current position. Calling the play() method can resume the + * animation from the last paused position. + */ + pause() { + this.jImageView.pause(); + } + + /** + * The total number of times the animation is set to play. The default is 1, which means the + * animation will play only once. If the repeat count is set to 0 or a negative value, the + * animation will play infinity times. + */ + repeatCount(): number { + return this.jImageView.repeatCount(); + } + + /** + * Set the number of times the animation to play. + */ + setRepeatCount(repeatCount: number) { + this.jImageView.setRepeatCount(repeatCount); + } + + /** + * Adds a listener to the set of listeners that are sent events through the life of an + * animation, such as start, repeat, and end. + */ + addListener(listener: WeakRef) { + this.listeners.push(listener); + } + + /** + * Removes a listener from the set listening to this animation. + */ + removeListener(listener: WeakRef) { + let index = this.listeners.indexOf(listener); + if (index != -1) { + this.listeners.splice(index); + } + } + + /** + * Renders the current image frame immediately. Note that all the changes previously made to the + * PAGImageView will only take effect after this method is called. If the play() method is + * already called, there is no need to call it manually since it will be automatically called + * every frame. Returns true if the content has changed. + */ + flush(): boolean { + return this.jImageView.flush(); + } + + uniqueID(): string { + return this.jImageView.uniqueID(); + } + + update(): void { + return this.jImageView.update(); + } + + constructor() { + this.jImageView.setStateChangeCallback(this.onAnimatorStateChange); + this.jImageView.setProgressUpdateCallback(this.onAnimatorProgressUpdate); + } + + private onAnimationStart() { + for (const weakListener of this.listeners) { + const listener = weakListener.deref(); + if (listener && listener.onAnimationStart) { + listener.onAnimationStart(this); + } + } + } + + private onAnimationEnd() { + for (const weakListener of this.listeners) { + const listener = weakListener.deref(); + if (listener && listener.onAnimationEnd) { + listener.onAnimationEnd(this); + } + } + } + + private onAnimationCancel() { + for (const weakListener of this.listeners) { + const listener = weakListener.deref(); + if (listener && listener.onAnimationCancel) { + listener.onAnimationCancel(this); + } + } + } + + private onAnimationRepeat() { + for (const weakListener of this.listeners) { + const listener = weakListener.deref(); + if (listener && listener.onAnimationRepeat) { + listener.onAnimationRepeat(this); + } + } + } + + attachToView(view: WeakRef) { + if (this.view != null && this.view != view) { + console.error("controller has attached to view"); + } + this.view = view; + } + + detachFromView() { + this.view = null; + } + + private composition: PAGComposition | null = null; + private filePath: string | null = null; + private listeners: Array> = []; + private view: WeakRef | null = null; + private jImageView: JPAGImageView = new JPAGImageView(); + private onAnimatorStateChange = (state: number): void => { + switch (state) { + case PAGAnimatorState.Start: + return this.onAnimationStart(); + case PAGAnimatorState.Cancel: + return this.onAnimationCancel(); + case PAGAnimatorState.End: + return this.onAnimationEnd(); + case PAGAnimatorState.Repeat: + return this.onAnimationRepeat(); + } + } + private onAnimatorProgressUpdate = (): void => { + for (const weakListener of this.listeners) { + const listener = weakListener.deref(); + if (listener && listener.onAnimationUpdate) { + listener.onAnimationUpdate(this); + } + } + } +} + +@Component +export struct PAGImageView { + @ObjectLink controller: PAGImageViewController; + + build() { + XComponent({ + id: this.controller?.uniqueID(), + type: XComponentType.SURFACE, + libraryname: "pag", + }) + .backgroundColor(Color.Transparent) + } + + onPageShow(): void { + this.controller.update(); + } + + aboutToAppear(): void { + this.controller.attachToView(new WeakRef(this)); + } + + aboutToDisappear(): void { + this.controller.detachFromView(); + } +} \ No newline at end of file diff --git a/src/platform/ohos/JPAG.cpp b/src/platform/ohos/JPAG.cpp index b5c76e0ffb..f2d75e2071 100644 --- a/src/platform/ohos/JPAG.cpp +++ b/src/platform/ohos/JPAG.cpp @@ -23,6 +23,7 @@ #include "platform/ohos/JPAGDiskCache.h" #include "platform/ohos/JPAGFont.h" #include "platform/ohos/JPAGImage.h" +#include "platform/ohos/JPAGImageView.h" #include "platform/ohos/JPAGLayerHandle.h" #include "platform/ohos/JPAGPlayer.h" #include "platform/ohos/JPAGSurface.h" @@ -64,7 +65,9 @@ static napi_value Init(napi_env env, napi_value exports) { pag::JPAGImage::Init(env, exports) && pag::JPAGPlayer::Init(env, exports) && pag::JPAGSurface::Init(env, exports) && pag::JPAGFont::Init(env, exports) && pag::JPAGText::Init(env, exports) && pag::JPAGImage::Init(env, exports) && - pag::JPAGView::Init(env, exports) && pag::JPAGDiskCache::Init(env, exports); + pag::JPAGView::Init(env, exports) && pag::JPAGImageView::Init(env, exports) && + pag::JPAGDiskCache::Init(env, exports) && + pag::XComponentHandler::Init(env, exports); if (!result) { LOGE("PAG InitFailed"); } diff --git a/src/platform/ohos/JPAGImageView.cpp b/src/platform/ohos/JPAGImageView.cpp new file mode 100644 index 0000000000..0870f0c6c6 --- /dev/null +++ b/src/platform/ohos/JPAGImageView.cpp @@ -0,0 +1,877 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// 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. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "JPAGImageView.h" +#include +#include +#include +#include +#include +#include "base/utils/TGFXCast.h" +#include "base/utils/TimeUtil.h" +#include "base/utils/UniqueID.h" +#include "platform/ohos/GPUDrawable.h" +#include "platform/ohos/JPAG.h" +#include "platform/ohos/JPAGLayerHandle.h" +#include "platform/ohos/JsHelper.h" +#include "rendering/utils/ApplyScaleMode.h" +#include "tgfx/core/ColorType.h" +#include "tgfx/platform/ohos/OHOSPixelMap.h" + +namespace pag { +static std::unordered_map> ViewMap = {}; + +static napi_value Flush(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + bool flushResult = false; + if (view != nullptr) { + flushResult = view->flush(); + } + napi_value result; + napi_get_boolean(env, flushResult, &result); + return result; +} + +static napi_value Update(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + auto animator = view->getAnimator(); + if (view != nullptr && animator != nullptr) { + animator->update(); + } + return nullptr; +} + +static napi_value SetComposition(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 2; + napi_value args[2] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + if (argc < 2) { + return nullptr; + } + auto layer = JPAGLayerHandle::FromJs(env, args[0]); + double frameRate = 30.0f; + napi_get_value_double(env, args[1], &frameRate); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view != nullptr) { + if (layer != nullptr && layer->layerType() == LayerType::PreCompose) { + view->setComposition(std::static_pointer_cast(layer), frameRate); + } else { + view->setComposition(nullptr, 30.0f); + } + } + return nullptr; +} + +static napi_value ScaleMode(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + int scaleMode = PAGScaleMode::LetterBox; + if (view != nullptr) { + scaleMode = view->scaleMode(); + } + napi_value result; + napi_create_int32(env, scaleMode, &result); + return result; +} + +static napi_value SetScaleMode(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 1; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + if (argc == 0) { + return nullptr; + } + int scaleMode = 0; + napi_get_value_int32(env, args[0], &scaleMode); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view != nullptr) { + view->setScaleMode(scaleMode); + } + return nullptr; +} + +static napi_value Matrix(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + auto matrix = Matrix::I(); + if (view != nullptr) { + matrix = view->matrix(); + } + return CreateMatrix(env, matrix); +} + +static napi_value SetMatrix(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 1; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + if (argc == 0) { + return nullptr; + } + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view != nullptr) { + view->setMatrix(GetMatrix(env, args[0])); + } + return nullptr; +} + +static napi_value CacheAllFramesInMemory(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + bool cacheAllFramesInMemory = false; + if (view != nullptr) { + cacheAllFramesInMemory = view->cacheAllFramesInMemory(); + } + napi_value result; + napi_get_boolean(env, cacheAllFramesInMemory, &result); + return result; +} + +static napi_value SetCacheAllFramesInMemory(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 1; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + if (argc == 0) { + return nullptr; + } + bool cacheAllFramesInMemory = false; + napi_get_value_bool(env, args[0], &cacheAllFramesInMemory); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view != nullptr) { + view->setCacheAllFramesInMemory(cacheAllFramesInMemory); + } + return nullptr; +} + +static napi_value RepeatCount(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + int repeatCount = 0; + if (view != nullptr) { + auto animator = view->getAnimator(); + if (animator != nullptr) { + repeatCount = animator->repeatCount(); + } + } + napi_value result; + napi_create_int32(env, repeatCount, &result); + return result; +} + +static napi_value SetRepeatCount(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 1; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + if (argc == 0) { + return nullptr; + } + int repeatCount = 0; + napi_get_value_int32(env, args[0], &repeatCount); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view != nullptr) { + auto animator = view->getAnimator(); + if (animator) { + animator->setRepeatCount(repeatCount); + } + } + return nullptr; +} + +static napi_value Play(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view != nullptr) { + auto animator = view->getAnimator(); + if (animator) { + animator->start(); + } + } + return nullptr; +} + +static napi_value IsPlaying(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + bool isPlaying = false; + if (view != nullptr) { + auto animator = view->getAnimator(); + if (animator != nullptr) { + isPlaying = animator->isRunning(); + } + } + napi_value result; + napi_get_boolean(env, isPlaying, &result); + return result; +} + +static napi_value Pause(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view != nullptr) { + auto animator = view->getAnimator(); + if (animator) { + animator->cancel(); + } + } + return nullptr; +} + +static napi_value UniqueID(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view == nullptr) { + return nullptr; + } + napi_value result; + napi_create_string_utf8(env, view->id.c_str(), view->id.length(), &result); + return result; +} + +static napi_value NumFrame(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view == nullptr) { + return nullptr; + } + napi_value result; + auto decoder = view->getDecoder(); + if (decoder) { + napi_create_int64(env, decoder->numFrames(), &result); + } else { + napi_create_int64(env, 0, &result); + } + return result; +} + +static void StateChangeCallback(napi_env env, napi_value callback, void*, void* data) { + int state = *static_cast(data); + size_t argc = 1; + napi_value argv[1] = {0}; + napi_create_uint32(env, state, &argv[0]); + napi_value undefined; + napi_get_undefined(env, &undefined); + napi_call_function(env, undefined, callback, argc, argv, nullptr); +} + +static napi_value SetStateChangeCallback(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 1; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + if (argc == 0) { + return nullptr; + } + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + + napi_value resourceName = nullptr; + napi_create_string_utf8(env, "PAGViewStateChangeCallback", NAPI_AUTO_LENGTH, &resourceName); + + napi_create_threadsafe_function(env, args[0], nullptr, resourceName, 0, 1, nullptr, nullptr, view, + StateChangeCallback, &view->playingStateCallback); + return nullptr; +} + +static void ProgressCallback(napi_env env, napi_value callback, void*, void*) { + napi_value undefined; + napi_get_undefined(env, &undefined); + napi_call_function(env, undefined, callback, 0, nullptr, nullptr); +} + +static napi_value SetProgressUpdateCallback(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 1; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + if (argc == 0) { + return nullptr; + } + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + napi_value resourceName = nullptr; + napi_create_string_utf8(env, "PAGViewProgressCallback", NAPI_AUTO_LENGTH, &resourceName); + napi_create_threadsafe_function(env, args[0], nullptr, resourceName, 0, 1, nullptr, nullptr, + nullptr, ProgressCallback, &view->progressCallback); + return nullptr; +} + +static napi_value RenderScale(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view == nullptr) { + return nullptr; + } + napi_value result; + napi_create_double(env, view->renderScale(), &result); + return result; +} + +static napi_value SetRenderScale(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 1; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + if (argc == 0) { + return nullptr; + } + double value = 1.0f; + napi_get_value_double(env, args[0], &value); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view != nullptr) { + view->setRenderScale(value); + } + return nullptr; +} + +static napi_value CurrentFrame(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + Frame currentFrame = 0; + if (view != nullptr) { + currentFrame = view->currentFrame(); + } + napi_value result; + napi_create_int64(env, currentFrame, &result); + return result; +} + +static napi_value SetCurrentFrame(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 1; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + if (argc == 0) { + return nullptr; + } + int64_t currentFrame = 0; + napi_get_value_int64(env, args[0], ¤tFrame); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view != nullptr) { + view->setCurrentFrame(currentFrame); + } + return nullptr; +} + +static napi_value CurrentImage(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view == nullptr) { + return nullptr; + } + return view->getCurrentPixelMap(env); +} + +static napi_value Release(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + JPAGImageView* view = nullptr; + napi_unwrap(env, jsView, reinterpret_cast(&view)); + if (view == nullptr) { + return nullptr; + } + view->release(); + return nullptr; +} + +napi_value JPAGImageView::Constructor(napi_env env, napi_callback_info info) { + napi_value jsView = nullptr; + size_t argc = 0; + napi_value args[1] = {0}; + napi_get_cb_info(env, info, &argc, args, &jsView, nullptr); + std::string id = "PAImageView" + std::to_string(UniqueID::Next()); + auto cView = std::make_shared(id); + cView->_animator = PAGAnimator::MakeFrom(cView); + XComponentHandler::AddListener(id, cView); + napi_wrap( + env, jsView, cView.get(), + [](napi_env, void* finalize_data, void*) { + JPAGImageView* view = static_cast(finalize_data); + XComponentHandler::RemoveListener(view->id); + ViewMap.erase(view->id); + }, + nullptr, nullptr); + ViewMap.emplace(id, cView); + return jsView; +} + +std::shared_ptr JPAGImageView::getDecoderInternal() { + if (targetWindow == nullptr || _composition == nullptr) { + invalidDecoder(); + return nullptr; + } + if (_decoder == nullptr) { + float scaleFactor = 1.0; + if (_width >= _height) { + scaleFactor = static_cast(_renderScale * (_width * 1.0 / _composition->width())); + } else { + scaleFactor = static_cast(_renderScale * (_height * 1.0 / _composition->height())); + } + _decoder = PAGDecoder::MakeFrom(_composition, _frameRate, scaleFactor); + refreshMatrixFromScaleMode(); + } + return _decoder; +} + +bool JPAGImageView::Init(napi_env env, napi_value exports) { + napi_property_descriptor classProp[] = { + PAG_DEFAULT_METHOD_ENTRY(flush, Flush), + PAG_DEFAULT_METHOD_ENTRY(setComposition, SetComposition), + PAG_DEFAULT_METHOD_ENTRY(scaleMode, ScaleMode), + PAG_DEFAULT_METHOD_ENTRY(setScaleMode, SetScaleMode), + PAG_DEFAULT_METHOD_ENTRY(matrix, Matrix), + PAG_DEFAULT_METHOD_ENTRY(setMatrix, SetMatrix), + PAG_DEFAULT_METHOD_ENTRY(cacheAllFramesInMemory, CacheAllFramesInMemory), + PAG_DEFAULT_METHOD_ENTRY(setCacheAllFramesInMemory, SetCacheAllFramesInMemory), + PAG_DEFAULT_METHOD_ENTRY(repeatCount, RepeatCount), + PAG_DEFAULT_METHOD_ENTRY(setRepeatCount, SetRepeatCount), + PAG_DEFAULT_METHOD_ENTRY(play, Play), + PAG_DEFAULT_METHOD_ENTRY(isPlaying, IsPlaying), + PAG_DEFAULT_METHOD_ENTRY(pause, Pause), + PAG_DEFAULT_METHOD_ENTRY(setStateChangeCallback, SetStateChangeCallback), + PAG_DEFAULT_METHOD_ENTRY(setProgressUpdateCallback, SetProgressUpdateCallback), + PAG_DEFAULT_METHOD_ENTRY(uniqueID, UniqueID), + PAG_DEFAULT_METHOD_ENTRY(setRenderScale, SetRenderScale), + PAG_DEFAULT_METHOD_ENTRY(renderScale, RenderScale), + PAG_DEFAULT_METHOD_ENTRY(currentFrame, CurrentFrame), + PAG_DEFAULT_METHOD_ENTRY(setCurrentFrame, SetCurrentFrame), + PAG_DEFAULT_METHOD_ENTRY(numFrame, NumFrame), + PAG_DEFAULT_METHOD_ENTRY(setCurrentFrame, SetCurrentFrame), + PAG_DEFAULT_METHOD_ENTRY(currentImage, CurrentImage), + PAG_DEFAULT_METHOD_ENTRY(update, Update), + PAG_DEFAULT_METHOD_ENTRY(release, Release)}; + auto status = DefineClass(env, exports, ClassName(), sizeof(classProp) / sizeof(classProp[0]), + classProp, Constructor, ""); + if (status != napi_ok) { + return false; + } + return true; +} + +void JPAGImageView::onAnimationStart(PAGAnimator*) { + std::lock_guard lock_guard(locker); + if (playingStateCallback) { + napi_call_threadsafe_function(playingStateCallback, const_cast(&PAGAnimatorState::Start), + napi_threadsafe_function_call_mode::napi_tsfn_nonblocking); + } +} + +void JPAGImageView::onAnimationCancel(PAGAnimator*) { + std::lock_guard lock_guard(locker); + if (playingStateCallback) { + napi_call_threadsafe_function(playingStateCallback, + const_cast(&PAGAnimatorState::Cancel), + napi_threadsafe_function_call_mode::napi_tsfn_nonblocking); + } +} + +void JPAGImageView::onAnimationEnd(PAGAnimator*) { + std::lock_guard lock_guard(locker); + if (playingStateCallback) { + napi_call_threadsafe_function(playingStateCallback, const_cast(&PAGAnimatorState::End), + napi_threadsafe_function_call_mode::napi_tsfn_nonblocking); + } +} + +void JPAGImageView::onAnimationRepeat(PAGAnimator*) { + std::lock_guard lock_guard(locker); + if (playingStateCallback) { + napi_call_threadsafe_function(playingStateCallback, + const_cast(&PAGAnimatorState::Repeat), + napi_threadsafe_function_call_mode::napi_tsfn_nonblocking); + } +} + +void JPAGImageView::onAnimationUpdate(PAGAnimator* animator) { + std::lock_guard lock_guard(locker); + if (progressCallback) { + napi_call_threadsafe_function(progressCallback, nullptr, + napi_threadsafe_function_call_mode::napi_tsfn_nonblocking); + } + Frame frame = 0; + auto decoder = getDecoderInternal(); + if (_composition != nullptr && decoder != nullptr) { + frame = ProgressToFrame(animator->progress(), _decoder->numFrames()); + } + handleFrame(frame); +} + +void JPAGImageView::onSurfaceCreated(NativeWindow* window) { + std::lock_guard lock_guard(locker); + if (_animator == nullptr) { + return; + } + _window = window; + targetWindow = tgfx::EGLWindow::MakeFrom(reinterpret_cast(_window)); + invalidSize(); + _animator->update(); +} + +void JPAGImageView::onSurfaceSizeChanged() { + std::lock_guard lock_guard(locker); + if (_animator == nullptr) { + return; + } + invalidSize(); +} + +void JPAGImageView::onSurfaceDestroyed() { + std::lock_guard lock_guard(locker); + _window = nullptr; + targetWindow = nullptr; + invalidSize(); +} + +std::shared_ptr JPAGImageView::getDecoder() { + std::lock_guard lock_guard(locker); + return getDecoderInternal(); +} + +void JPAGImageView::invalidSize() { + if (targetWindow && _window) { + targetWindow->invalidSize(); + OH_NativeWindow_NativeWindowHandleOpt(_window, GET_BUFFER_GEOMETRY, &_height, &_width); + } else { + _width = 0; + _height = 0; + } + invalidDecoder(); +} + +void JPAGImageView::invalidDecoder() { + _decoder = nullptr; + images.clear(); +} + +std::shared_ptr JPAGImageView::getAnimator() { + std::lock_guard lock_guard(locker); + return _animator; +} + +void JPAGImageView::setCurrentFrame(Frame currentFrame) { + std::lock_guard lock_guard(locker); + if (_animator == nullptr || _composition == nullptr || _decoder == nullptr) { + return; + } + _animator->setProgress(FrameToProgress(currentFrame, _decoder->numFrames())); +} + +Frame JPAGImageView::currentFrame() { + std::lock_guard lock_guard(locker); + if (_animator == nullptr || _decoder == nullptr) { + return 0; + } + return ProgressToFrame(_animator->progress(), _decoder->numFrames()); +} + +void JPAGImageView::setComposition(std::shared_ptr composition, float frameRate) { + std::lock_guard lock_guard(locker); + if (_animator == nullptr) { + return; + } + if (composition != nullptr) { + _animator->setDuration(composition->duration()); + } else { + _animator->setDuration(0); + } + _composition = composition; + _frameRate = frameRate; + invalidDecoder(); +} + +void JPAGImageView::setScaleMode(int scaleMode) { + std::lock_guard lock_guard(locker); + _scaleMode = scaleMode; + refreshMatrixFromScaleMode(); +} + +int JPAGImageView::scaleMode() { + return _scaleMode; +} + +void JPAGImageView::setMatrix(const class Matrix& matrix) { + std::lock_guard lock_guard(locker); + _matrix = ToTGFX(matrix); + _scaleMode = PAGScaleMode::None; +} + +class Matrix JPAGImageView::matrix() { + std::lock_guard lock_guard(locker); + return ToPAG(_matrix); +} + +void JPAGImageView::setRenderScale(float renderScale) { + std::lock_guard lock_guard(locker); + if (renderScale <= 0.0 || renderScale > 1.0) { + renderScale = 1.0; + } + if (_renderScale == renderScale) { + return; + } + _renderScale = renderScale; + invalidDecoder(); +} + +float JPAGImageView::renderScale() { + return _renderScale; +} + +void JPAGImageView::setCacheAllFramesInMemory(bool cacheAllFramesInMemory) { + std::lock_guard lock_guard(locker); + if (_cacheAllFramesInMemory == cacheAllFramesInMemory) { + return; + } + _cacheAllFramesInMemory = cacheAllFramesInMemory; + if (!_cacheAllFramesInMemory) { + images.clear(); + } +} + +bool JPAGImageView::cacheAllFramesInMemory() { + return _cacheAllFramesInMemory; +} + +bool JPAGImageView::flush() { + std::lock_guard lock_guard(locker); + auto decoder = getDecoderInternal(); + if (decoder && _animator) { + return handleFrame(ProgressToFrame(_animator->progress(), decoder->numFrames())); + } + return false; +} + +void JPAGImageView::refreshMatrixFromScaleMode() { + if (_scaleMode == PAGScaleMode::None) { + return; + } + if (_decoder == nullptr) { + return; + } + + _matrix = ToTGFX(ApplyScaleMode(_scaleMode, _decoder->width() / _renderScale, + _decoder->height() / _renderScale, _width, _height)); +} + +bool JPAGImageView::handleFrame(Frame frame) { + auto decoder = getDecoderInternal(); + if (!decoder) { + return false; + } + if (!decoder->checkFrameChanged(frame)) { + return true; + } + auto image = getImage(frame); + if (!image.second) { + return false; + } + currentBitmap = image.first; + currentImage = image.second; + return present(currentImage); +} + +std::pair> JPAGImageView::getImage(Frame frame) { + if (_cacheAllFramesInMemory && images.find(frame) != images.end()) { + return images[frame]; + } + + tgfx::Bitmap bitmap; + if (!bitmap.allocPixels(_decoder->width(), _decoder->height(), false, false)) { + return {{}, nullptr}; + } + auto pixels = bitmap.lockPixels(); + if (pixels == nullptr) { + return {{}, nullptr}; + } + _decoder->readFrame(frame, pixels, bitmap.rowBytes()); + bitmap.unlockPixels(); + auto image = tgfx::Image::MakeFrom(bitmap); + + if (image == nullptr) { + return {{}, nullptr}; + } + if (_cacheAllFramesInMemory) { + images[frame] = {bitmap, image}; + } + return {bitmap, image}; +} + +bool JPAGImageView::present(std::shared_ptr image) { + if (!targetWindow && image) { + return false; + } + auto device = targetWindow->getDevice(); + if (!device) { + return false; + } + auto context = device->lockContext(); + if (!context) { + return false; + } + auto surface = targetWindow->getSurface(context, true); + if (surface == nullptr) { + surface = targetWindow->getSurface(context, false); + } + if (!surface) { + device->unlock(); + return false; + } + auto canvas = surface->getCanvas(); + if (!canvas) { + device->unlock(); + return false; + } + canvas->clear(); + tgfx::Matrix imageMatrix = tgfx::Matrix::MakeScale(1.0 / _renderScale); + imageMatrix.postConcat(_matrix); + canvas->drawImage(image, imageMatrix); + surface->flush(); + context->submit(); + targetWindow->present(context); + context->purgeResourcesNotUsedSince(std::chrono::steady_clock::now()); + device->unlock(); + return true; +} + +napi_value JPAGImageView::getCurrentPixelMap(napi_env env) { + std::lock_guard lock_guard(locker); + if (currentBitmap.isEmpty()) { + return nullptr; + } + + // create PixelMap + OhosPixelMapCreateOps ops; + ops.width = currentBitmap.width(); + ops.height = currentBitmap.height(); + ops.pixelFormat = currentBitmap.colorType() == tgfx::ColorType::RGBA_8888 + ? PIXEL_FORMAT_RGBA_8888 + : PIXEL_FORMAT_BGRA_8888; + ops.alphaType = OHOS_PIXEL_MAP_ALPHA_TYPE_PREMUL; + ops.editable = false; + napi_value pixelMap; + auto pixels = currentBitmap.lockPixels(); + auto status = OH_PixelMap_CreatePixelMapWithStride(env, ops, pixels, currentBitmap.byteSize(), + currentBitmap.rowBytes(), &pixelMap); + currentBitmap.unlockPixels(); + + // readPixels + auto nativePixelMap = OH_PixelMap_InitNativePixelMap(env, pixelMap); + if (!nativePixelMap) { + return nullptr; + } + void* pixelMapAddress = nullptr; + OH_PixelMap_AccessPixels(nativePixelMap, &pixelMapAddress); + currentBitmap.readPixels(currentBitmap.info(), pixelMapAddress); + OH_PixelMap_UnAccessPixels(nativePixelMap); + if (status == napi_ok) { + return pixelMap; + } else { + return nullptr; + } +} + +void JPAGImageView::release() { + std::lock_guard lock_guard(locker); + if (progressCallback != nullptr) { + napi_release_threadsafe_function(progressCallback, napi_tsfn_abort); + progressCallback = nullptr; + } + if (playingStateCallback != nullptr) { + napi_release_threadsafe_function(playingStateCallback, napi_tsfn_abort); + playingStateCallback = nullptr; + } + + if (_animator) { + _animator = nullptr; + } + + invalidDecoder(); +} + +} // namespace pag \ No newline at end of file diff --git a/src/platform/ohos/JPAGImageView.h b/src/platform/ohos/JPAGImageView.h new file mode 100644 index 0000000000..7a5df6aab6 --- /dev/null +++ b/src/platform/ohos/JPAGImageView.h @@ -0,0 +1,132 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// 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. +// +///////////////////////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include +#include "pag/pag.h" +#include "platform/ohos/XComponentHandler.h" +#include "rendering/PAGAnimator.h" +#include "tgfx/gpu/Window.h" + +namespace pag { +class JPAGImageView : public PAGAnimator::Listener, public XComponentListener { + public: + static bool Init(napi_env env, napi_value exports); + static inline std::string ClassName() { + return "JPAGImageView"; + } + + explicit JPAGImageView(const std::string& id) : id(std::move(id)) { + } + + virtual ~JPAGImageView() { + release(); + } + + void onAnimationStart(PAGAnimator*) override; + + void onAnimationCancel(PAGAnimator*) override; + + void onAnimationEnd(PAGAnimator*) override; + + void onAnimationRepeat(PAGAnimator*) override; + + void onAnimationUpdate(PAGAnimator* animator) override; + + void onSurfaceCreated(NativeWindow* window) override; + + void onSurfaceSizeChanged() override; + + void onSurfaceDestroyed() override; + + std::shared_ptr getDecoder(); + + std::shared_ptr getAnimator(); + + void setCurrentFrame(Frame currentFrame); + + Frame currentFrame(); + + void setComposition(std::shared_ptr composition, float frameRate); + + void setScaleMode(int scaleMode); + + int scaleMode(); + + void setMatrix(const Matrix& matrix); + + Matrix matrix(); + + void setRenderScale(float renderScale); + + float renderScale(); + + void setCacheAllFramesInMemory(bool cacheAllFramesInMemory); + + bool cacheAllFramesInMemory(); + + bool flush(); + + napi_value getCurrentPixelMap(napi_env env); + + void release(); + + std::string id; + + napi_threadsafe_function progressCallback = nullptr; + napi_threadsafe_function playingStateCallback = nullptr; + + private: + static napi_value Constructor(napi_env env, napi_callback_info info); + + std::shared_ptr getDecoderInternal(); + + void invalidSize(); + + void invalidDecoder(); + + void refreshMatrixFromScaleMode(); + + bool handleFrame(Frame frame); + + std::pair> getImage(Frame frame); + + bool present(std::shared_ptr image); + + std::mutex locker; + int _width = 0; + int _height = 0; + float _renderScale = 1.0f; + float _frameRate = 30.0f; + int _scaleMode = PAGScaleMode::LetterBox; + tgfx::Matrix _matrix = tgfx::Matrix::I(); + bool _cacheAllFramesInMemory = false; + std::shared_ptr _composition = nullptr; + std::shared_ptr _animator = nullptr; + std::shared_ptr _decoder = nullptr; + + NativeWindow* _window = nullptr; + std::shared_ptr targetWindow = nullptr; + + std::shared_ptr currentImage = nullptr; + tgfx::Bitmap currentBitmap; + + std::unordered_map>> images; +}; +} // namespace pag diff --git a/src/platform/ohos/JPAGView.cpp b/src/platform/ohos/JPAGView.cpp index 047a429124..86f91e540b 100644 --- a/src/platform/ohos/JPAGView.cpp +++ b/src/platform/ohos/JPAGView.cpp @@ -19,106 +19,16 @@ #include "JPAGView.h" #include #include -#include "base/utils/Log.h" #include "base/utils/UniqueID.h" #include "platform/ohos/GPUDrawable.h" #include "platform/ohos/JPAGLayerHandle.h" #include "platform/ohos/JsHelper.h" +#include "platform/ohos/XComponentHandler.h" namespace pag { -static int PAGViewStateStart = 0; -static int PAGViewStateCancel = 1; -static int PAGViewStateEnd = 2; -static int PAGViewStateRepeat = 3; static std::unordered_map> ViewMap = {}; -void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window) { - if ((component == nullptr) || (window == nullptr)) { - return; - } - - char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; - uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; - if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != - OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { - return; - } - - std::string id(idStr); - if (ViewMap.find(id) == ViewMap.end()) { - LOGE("Could not find PAGView on Surface Created id:%s", id.c_str()); - return; - } - auto drawable = pag::GPUDrawable::FromWindow(static_cast(window)); - auto view = ViewMap[id]; - auto player = view->getPlayer(); - auto animator = view->getAnimator(); - if (player == nullptr || animator == nullptr) { - return; - } - player->setSurface(pag::PAGSurface::MakeFrom(drawable)); - animator->update(); -} - -void OnSurfaceChangedCB(OH_NativeXComponent* component, void* window) { - if ((component == nullptr) || (window == nullptr)) { - return; - } - - char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; - uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; - if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != - OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { - return; - } - - std::string id(idStr); - if (ViewMap.find(id) == ViewMap.end()) { - LOGE("Could not find PAGView on Surface change id:%s", id.c_str()); - return; - } - auto view = ViewMap[id]; - auto player = view->getPlayer(); - if (player == nullptr) { - return; - } - auto surface = player->getSurface(); - if (!surface) { - LOGE("PAGView id:%s without PAGSurface while surface size change", id.c_str()); - return; - } - surface->updateSize(); -} - -void OnSurfaceDestroyedCB(OH_NativeXComponent* component, void* window) { - if ((component == nullptr) || (window == nullptr)) { - return; - } - - char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; - uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; - if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != - OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { - return; - } - - std::string id(idStr); - if (ViewMap.find(id) == ViewMap.end()) { - LOGE("Could not find PAGView on Surface Destroyed id:%s", id.c_str()); - return; - } - auto view = ViewMap[id]; - auto player = view->getPlayer(); - if (player == nullptr) { - return; - } - player->setSurface(nullptr); -} - -static void DispatchTouchEventCB(OH_NativeXComponent*, void*) { -} - static napi_value Flush(napi_env env, napi_callback_info info) { napi_value jsView = nullptr; size_t argc = 0; @@ -848,10 +758,12 @@ napi_value JPAGView::Constructor(napi_env env, napi_callback_info info) { std::string id = "PAGView" + std::to_string(UniqueID::Next()); auto cView = std::make_shared(id); cView->animator = PAGAnimator::MakeFrom(cView); + XComponentHandler::AddListener(id, cView); napi_wrap( env, jsView, cView.get(), [](napi_env, void* finalize_data, void*) { JPAGView* view = static_cast(finalize_data); + XComponentHandler::RemoveListener(view->id); ViewMap.erase(view->id); }, nullptr, nullptr); @@ -901,24 +813,6 @@ bool JPAGView::Init(napi_env env, napi_value exports) { if (status != napi_ok) { return false; } - napi_value exportInstance = nullptr; - if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { - return true; - } - - OH_NativeXComponent* nativeXComponent = nullptr; - auto temp = napi_unwrap(env, exportInstance, reinterpret_cast(&nativeXComponent)); - if (temp != napi_ok) { - return true; - } - - static OH_NativeXComponent_Callback renderCallback; - renderCallback.OnSurfaceCreated = OnSurfaceCreatedCB; - renderCallback.OnSurfaceChanged = OnSurfaceChangedCB; - renderCallback.OnSurfaceDestroyed = OnSurfaceDestroyedCB; - renderCallback.DispatchTouchEvent = DispatchTouchEventCB; - - OH_NativeXComponent_RegisterCallback(nativeXComponent, &renderCallback); return true; } @@ -929,7 +823,7 @@ JPAGView::~JPAGView() { void JPAGView::onAnimationStart(PAGAnimator*) { std::lock_guard lock_guard(locker); if (playingStateCallback) { - napi_call_threadsafe_function(playingStateCallback, &PAGViewStateStart, + napi_call_threadsafe_function(playingStateCallback, const_cast(&PAGAnimatorState::Start), napi_threadsafe_function_call_mode::napi_tsfn_nonblocking); } } @@ -937,7 +831,8 @@ void JPAGView::onAnimationStart(PAGAnimator*) { void JPAGView::onAnimationCancel(PAGAnimator*) { std::lock_guard lock_guard(locker); if (playingStateCallback) { - napi_call_threadsafe_function(playingStateCallback, &PAGViewStateCancel, + napi_call_threadsafe_function(playingStateCallback, + const_cast(&PAGAnimatorState::Cancel), napi_threadsafe_function_call_mode::napi_tsfn_nonblocking); } } @@ -945,7 +840,7 @@ void JPAGView::onAnimationCancel(PAGAnimator*) { void JPAGView::onAnimationEnd(PAGAnimator*) { std::lock_guard lock_guard(locker); if (playingStateCallback) { - napi_call_threadsafe_function(playingStateCallback, &PAGViewStateEnd, + napi_call_threadsafe_function(playingStateCallback, const_cast(&PAGAnimatorState::End), napi_threadsafe_function_call_mode::napi_tsfn_nonblocking); } } @@ -953,7 +848,8 @@ void JPAGView::onAnimationEnd(PAGAnimator*) { void JPAGView::onAnimationRepeat(PAGAnimator*) { std::lock_guard lock_guard(locker); if (playingStateCallback) { - napi_call_threadsafe_function(playingStateCallback, &PAGViewStateRepeat, + napi_call_threadsafe_function(playingStateCallback, + const_cast(&PAGAnimatorState::Repeat), napi_threadsafe_function_call_mode::napi_tsfn_nonblocking); } } @@ -971,6 +867,35 @@ void JPAGView::onAnimationUpdate(PAGAnimator* animator) { } } +void JPAGView::onSurfaceCreated(NativeWindow* window) { + std::lock_guard lock_guard(locker); + auto drawable = pag::GPUDrawable::FromWindow(window); + if (player == nullptr || animator == nullptr) { + return; + } + player->setSurface(pag::PAGSurface::MakeFrom(drawable)); + animator->update(); +} + +void JPAGView::onSurfaceSizeChanged() { + std::lock_guard lock_guard(locker); + if (player == nullptr) { + return; + } + auto surface = player->getSurface(); + if (surface) { + surface->updateSize(); + } +} + +void JPAGView::onSurfaceDestroyed() { + std::lock_guard lock_guard(locker); + if (player == nullptr) { + return; + } + player->setSurface(nullptr); +} + void JPAGView::release() { std::lock_guard lock_guard(locker); if (progressCallback != nullptr) { diff --git a/src/platform/ohos/JPAGView.h b/src/platform/ohos/JPAGView.h index 5df1464fb6..7a28b970ae 100644 --- a/src/platform/ohos/JPAGView.h +++ b/src/platform/ohos/JPAGView.h @@ -19,10 +19,11 @@ #include #include "pag/pag.h" +#include "platform/ohos/XComponentHandler.h" #include "rendering/PAGAnimator.h" namespace pag { -class JPAGView : public PAGAnimator::Listener { +class JPAGView : public PAGAnimator::Listener, public XComponentListener { public: static bool Init(napi_env env, napi_value exports); static inline std::string ClassName() { @@ -44,6 +45,12 @@ class JPAGView : public PAGAnimator::Listener { void onAnimationUpdate(PAGAnimator* animator) override; + void onSurfaceCreated(NativeWindow* window) override; + + void onSurfaceDestroyed() override; + + void onSurfaceSizeChanged() override; + void release(); std::shared_ptr getPlayer(); diff --git a/src/platform/ohos/JsHelper.cpp b/src/platform/ohos/JsHelper.cpp index 1596acee57..49c2e7b506 100644 --- a/src/platform/ohos/JsHelper.cpp +++ b/src/platform/ohos/JsHelper.cpp @@ -326,13 +326,13 @@ napi_value MakeSnapshot(napi_env env, PAGSurface* surface) { } tgfx::ImageInfo imageInfo = tgfx::ImageInfo::Make(surface->width(), surface->height(), tgfx::ColorType::BGRA_8888); - uint8_t* pixels = new (std::nothrow) uint8_t[imageInfo.byteSize()]; + + auto pixels = ByteData::Make(imageInfo.byteSize()); if (pixels == nullptr) { return nullptr; } - if (!surface->readPixels(ColorType::BGRA_8888, AlphaType::Premultiplied, pixels, + if (!surface->readPixels(ColorType::BGRA_8888, AlphaType::Premultiplied, pixels->data(), imageInfo.rowBytes())) { - delete[] pixels; return nullptr; } @@ -343,7 +343,8 @@ napi_value MakeSnapshot(napi_env env, PAGSurface* surface) { ops.alphaType = OHOS_PIXEL_MAP_ALPHA_TYPE_PREMUL; ops.editable = true; napi_value pixelMap; - auto status = OH_PixelMap_CreatePixelMap(env, ops, pixels, imageInfo.byteSize(), &pixelMap); + auto status = OH_PixelMap_CreatePixelMapWithStride(env, ops, pixels->data(), imageInfo.byteSize(), + imageInfo.rowBytes(), &pixelMap); if (status == napi_ok) { return pixelMap; } diff --git a/src/platform/ohos/JsHelper.h b/src/platform/ohos/JsHelper.h index b61aaf4d2a..e72ff5498c 100644 --- a/src/platform/ohos/JsHelper.h +++ b/src/platform/ohos/JsHelper.h @@ -29,6 +29,14 @@ namespace pag { +class PAGAnimatorState { + public: + inline static const Enum Start = 0; + inline static const Enum Cancel = 1; + inline static const Enum End = 2; + inline static const Enum Repeat = 3; +}; + napi_status DefineClass(napi_env env, napi_value exports, const std::string& utf8name, size_t propertyCount, const napi_property_descriptor* properties, napi_callback constructor, const std::string& parentName); diff --git a/src/platform/ohos/XComponentHandler.cpp b/src/platform/ohos/XComponentHandler.cpp new file mode 100644 index 0000000000..894a4a93ba --- /dev/null +++ b/src/platform/ohos/XComponentHandler.cpp @@ -0,0 +1,126 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// 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. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "XComponentHandler.h" +#include + +namespace pag { + +std::unordered_map> XComponentListeners; + +void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window) { + if ((component == nullptr) || (window == nullptr)) { + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != + OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + return; + } + + std::string id(idStr); + if (XComponentListeners.find(id) == XComponentListeners.end()) { + return; + } + auto listener = XComponentListeners[id].lock(); + if (listener) { + listener->onSurfaceCreated(static_cast(window)); + } +} + +void OnSurfaceChangedCB(OH_NativeXComponent* component, void* window) { + if ((component == nullptr) || (window == nullptr)) { + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != + OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + return; + } + std::string id(idStr); + auto listener = XComponentListeners[id].lock(); + if (listener) { + listener->onSurfaceSizeChanged(); + } +} + +void OnSurfaceDestroyedCB(OH_NativeXComponent* component, void* window) { + if ((component == nullptr) || (window == nullptr)) { + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != + OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + return; + } + + std::string id(idStr); + auto listener = XComponentListeners[id].lock(); + if (listener) { + listener->onSurfaceDestroyed(); + } +} + +static void DispatchTouchEventCB(OH_NativeXComponent*, void*) { +} + +bool XComponentHandler::Init(napi_env env, napi_value exports) { + napi_value exportInstance = nullptr; + if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { + return true; + } + + OH_NativeXComponent* nativeXComponent = nullptr; + auto status = napi_unwrap(env, exportInstance, reinterpret_cast(&nativeXComponent)); + if (status != napi_ok) { + return true; + } + + static OH_NativeXComponent_Callback renderCallback; + renderCallback.OnSurfaceCreated = OnSurfaceCreatedCB; + renderCallback.OnSurfaceChanged = OnSurfaceChangedCB; + renderCallback.OnSurfaceDestroyed = OnSurfaceDestroyedCB; + renderCallback.DispatchTouchEvent = DispatchTouchEventCB; + OH_NativeXComponent_RegisterCallback(nativeXComponent, &renderCallback); + return true; +} + +bool XComponentHandler::AddListener(const std::string& xComponentID, + std::weak_ptr listener) { + if (XComponentListeners.find(xComponentID) != XComponentListeners.end()) { + return false; + } + XComponentListeners[xComponentID] = listener; + return true; +} + +bool XComponentHandler::RemoveListener(const std::string& xComponentID) { + if (XComponentListeners.find(xComponentID) == XComponentListeners.end()) { + return false; + } + XComponentListeners.erase(xComponentID); + return true; +} + +} // namespace pag \ No newline at end of file diff --git a/src/platform/ohos/XComponentHandler.h b/src/platform/ohos/XComponentHandler.h new file mode 100644 index 0000000000..4dae1756b0 --- /dev/null +++ b/src/platform/ohos/XComponentHandler.h @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// 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. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once +#include +#include +#include + +namespace pag { +class XComponentListener { + public: + virtual ~XComponentListener(){}; + virtual void onSurfaceCreated(NativeWindow* window) = 0; + virtual void onSurfaceSizeChanged() = 0; + virtual void onSurfaceDestroyed() = 0; +}; + +class XComponentHandler { + public: + static bool Init(napi_env env, napi_value exports); + static bool AddListener(const std::string& xComponentID, + std::weak_ptr listener); + static bool RemoveListener(const std::string& xComponentID); +}; +} // namespace pag \ No newline at end of file