From 9c7985415b32a5a48d0cb6b831b461cf124e290b Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Wed, 25 Dec 2024 16:36:51 +0800 Subject: [PATCH] refactor: implement SvgImage component with C-API (#322) Signed-off-by: Louis-C7 --- .../src/elements/Image.tsx | 118 ------ .../src/elements/Shape.tsx | 345 ------------------ .../src/fabric/NativeSvgImageModule.ts | 10 - .../harmony/svg/src/main/cpp/CMakeLists.txt | 5 + .../RNSVGBaseComponentInstance.h | 1 - .../RNSVGImageComponentInstance.cpp | 65 +++- .../RNSVGImageComponentInstance.h | 17 +- .../cpp/downloadUtils/HttpTaskProcessor.cpp | 207 +++++++++++ .../cpp/downloadUtils/HttpTaskProcessor.h | 88 +++++ .../cpp/downloadUtils/ImageSourceResolver.cpp | 106 ++++++ .../cpp/downloadUtils/ImageSourceResolver.h | 97 +++++ .../svg/src/main/cpp/downloadUtils/LRUCache.h | 98 +++++ .../cpp/turboModules/RNSVGImageModule.cpp | 43 ++- .../main/cpp/turboModules/RNSVGImageModule.h | 15 +- .../svg/src/main/cpp/utils/StringUtils.h | 38 +- tester/harmony/svg/src/main/ets/Logger.ts | 46 --- .../svg/src/main/ets/RNSVGImageModule.ts | 79 +--- 17 files changed, 770 insertions(+), 608 deletions(-) delete mode 100644 react-native-harmony-svg/src/elements/Image.tsx delete mode 100644 react-native-harmony-svg/src/elements/Shape.tsx delete mode 100644 react-native-harmony-svg/src/fabric/NativeSvgImageModule.ts create mode 100644 tester/harmony/svg/src/main/cpp/downloadUtils/HttpTaskProcessor.cpp create mode 100644 tester/harmony/svg/src/main/cpp/downloadUtils/HttpTaskProcessor.h create mode 100644 tester/harmony/svg/src/main/cpp/downloadUtils/ImageSourceResolver.cpp create mode 100644 tester/harmony/svg/src/main/cpp/downloadUtils/ImageSourceResolver.h create mode 100644 tester/harmony/svg/src/main/cpp/downloadUtils/LRUCache.h delete mode 100644 tester/harmony/svg/src/main/ets/Logger.ts diff --git a/react-native-harmony-svg/src/elements/Image.tsx b/react-native-harmony-svg/src/elements/Image.tsx deleted file mode 100644 index e58b9ba..0000000 --- a/react-native-harmony-svg/src/elements/Image.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import * as React from 'react'; -import type { ImageProps as RNImageProps, NativeMethods, ImageResolvedAssetSource } from 'react-native'; -import { Image } from 'react-native'; -import { alignEnum, meetOrSliceTypes } from 'react-native-svg/src/lib/extract/extractViewBox'; -import { - stringifyPropsForFabric, - withoutXY, -} from 'react-native-svg/src/lib/extract/extractProps'; -import type { CommonPathProps, NumberProp } from 'react-native-svg/src/lib/extract/types'; -import Shape from './Shape'; -import RNSVGImage from 'react-native-svg/src/fabric/ImageNativeComponent'; -import RNSVGImageModule from '../fabric/NativeSvgImageModule'; - -const spacesRegExp = /\s+/; - -export interface ImageProps extends CommonPathProps { - x?: NumberProp; - y?: NumberProp; - width?: NumberProp; - height?: NumberProp; - xlinkHref?: RNImageProps['source'] | string; - href?: RNImageProps['source'] | string; - preserveAspectRatio?: string; - opacity?: NumberProp; -} - -export default class SvgImage extends Shape { - static displayName = 'Image'; - - static defaultProps = { - x: 0, - y: 0, - width: 0, - height: 0, - preserveAspectRatio: 'xMidYMid meet', - }; - - constructor(props: ImageProps) { - super(props); - this.state = { - href: props.href || props.xlinkHref, - }; - if (this.state.href && typeof this.state.href === 'string' && this.state.href.startsWith('http')) { - this.fetchBase64String(this.state.href); - } - } - - fetchBase64String = (href: string) => { - const src = !href - ? null - : Image.resolveAssetSource( - typeof href === 'string' ? { uri: href } : href - ) - if (src) { - RNSVGImageModule.getBase64String(src.uri).then((res) => { - this.setState({ href: res }); - } - ).catch((error) => { - console.error(`getBase64String error: ${error.message}`); - }); - } - - }; - - componentDidUpdate(prevProps: ImageProps) { - if (prevProps.href !== this.props.href) { - const href = this.props.href || this.props.xlinkHref; - if (href && typeof href === 'string' && href.startsWith('http')) { - this.fetchBase64String(href); - } else { - this.setState({ href: href }) - } - } - } - - render() { - const { props } = this; - const { - preserveAspectRatio, - x, - y, - width, - height, - xlinkHref, - href = xlinkHref, - } = props; - const modes = preserveAspectRatio - ? preserveAspectRatio.trim().split(spacesRegExp) - : []; - const align = modes[0]; - const meetOrSlice: 'meet' | 'slice' | 'none' | string | undefined = - modes[1]; - const stringifiedImageProps = stringifyPropsForFabric({ - x, - y, - width, - height, - }); - const imageProps = { - ...stringifiedImageProps, - meetOrSlice: meetOrSliceTypes[meetOrSlice] || 0, - align: alignEnum[align] || 'xMidYMid', - src: !this.state.href - ? null - : Image.resolveAssetSource( - typeof this.state.href === 'string' ? { uri: this.state.href } : this.state.href - ), - }; - - return ( - this.refMethod(ref as (SvgImage & NativeMethods) | null)} - {...withoutXY(this, props)} - {...imageProps} - /> - ); - } -} diff --git a/react-native-harmony-svg/src/elements/Shape.tsx b/react-native-harmony-svg/src/elements/Shape.tsx deleted file mode 100644 index c62e0c0..0000000 --- a/react-native-harmony-svg/src/elements/Shape.tsx +++ /dev/null @@ -1,345 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -import { Component } from 'react'; -import SvgTouchableMixin from 'react-native-svg/src/lib/SvgTouchableMixin'; -import extractBrush from 'react-native-svg/src/lib/extract/extractBrush'; -import type { ColorValue, NativeMethods } from 'react-native'; -import { findNodeHandle } from 'react-native'; -import type { - ColumnMajorTransformMatrix, - TransformProps, -} from 'react-native-svg/src/lib/extract/types'; -import type { Spec } from 'react-native-svg/src/fabric/NativeSvgRenderableModule'; - -export interface SVGBoundingBoxOptions { - fill?: boolean; - stroke?: boolean; - markers?: boolean; - clipped?: boolean; -} - -export interface DOMPointInit { - x?: number; - y?: number; - z?: number; - w?: number; -} - -export interface Point { - x: number; - y: number; -} - -export interface SVGPoint extends Point { - matrixTransform(matrix: Matrix): SVGPoint; -} - -export interface Rect { - x: number; - y: number; - width: number; - height: number; -} -export type SVGRect = Rect; - -export interface Matrix { - a: number; - b: number; - c: number; - d: number; - e: number; - f: number; -} - -export interface SVGMatrix extends Matrix { - multiply(secondMatrix: Matrix): SVGMatrix; - inverse(): SVGMatrix; - translate(x: number, y: number): SVGMatrix; - scale(scaleFactor: number): SVGMatrix; - scaleNonUniform(scaleFactorX: number, scaleFactorY: number): SVGMatrix; - rotate(angle: number): SVGMatrix; - rotateFromVector(x: number, y: number): SVGMatrix; - flipX(): SVGMatrix; - flipY(): SVGMatrix; - skewX(angle: number): SVGMatrix; - skewY(angle: number): SVGMatrix; -} - -export function multiplyMatrices(l: Matrix, r: Matrix): Matrix { - const { a: al, b: bl, c: cl, d: dl, e: el, f: fl } = l; - const { a: ar, b: br, c: cr, d: dr, e: er, f: fr } = r; - - const a = al * ar + cl * br; - const c = al * cr + cl * dr; - const e = al * er + cl * fr + el; - const b = bl * ar + dl * br; - const d = bl * cr + dl * dr; - const f = bl * er + dl * fr + fl; - - return { a, c, e, b, d, f }; -} - -export function invert({ a, b, c, d, e, f }: Matrix): Matrix { - const n = a * d - b * c; - return { - a: d / n, - b: -b / n, - c: -c / n, - d: a / n, - e: (c * f - d * e) / n, - f: -(a * f - b * e) / n, - }; -} - -const deg2rad = Math.PI / 180; - -export class SVGMatrix implements SVGMatrix { - constructor(matrix?: Matrix) { - if (matrix) { - const { a, b, c, d, e, f } = matrix; - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.e = e; - this.f = f; - } else { - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.e = 0; - this.f = 0; - } - } - - multiply(secondMatrix: Matrix): SVGMatrix { - return new SVGMatrix(multiplyMatrices(this, secondMatrix)); - } - - inverse(): SVGMatrix { - return new SVGMatrix(invert(this)); - } - - translate(x: number, y: number): SVGMatrix { - return new SVGMatrix( - multiplyMatrices(this, { a: 1, b: 0, c: 0, d: 1, e: x, f: y }) - ); - } - - scale(scaleFactor: number): SVGMatrix { - return new SVGMatrix( - multiplyMatrices(this, { - a: scaleFactor, - b: 0, - c: 0, - d: scaleFactor, - e: 0, - f: 0, - }) - ); - } - - scaleNonUniform(scaleFactorX: number, scaleFactorY: number): SVGMatrix { - return new SVGMatrix( - multiplyMatrices(this, { - a: scaleFactorX, - b: 0, - c: 0, - d: scaleFactorY, - e: 0, - f: 0, - }) - ); - } - - rotate(angle: number): SVGMatrix { - const cos = Math.cos(deg2rad * angle); - const sin = Math.sin(deg2rad * angle); - return new SVGMatrix( - multiplyMatrices(this, { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 }) - ); - } - - rotateFromVector(x: number, y: number): SVGMatrix { - const angle = Math.atan2(y, x); - const cos = Math.cos(deg2rad * angle); - const sin = Math.sin(deg2rad * angle); - return new SVGMatrix( - multiplyMatrices(this, { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 }) - ); - } - - flipX(): SVGMatrix { - return new SVGMatrix( - multiplyMatrices(this, { a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 }) - ); - } - - flipY(): SVGMatrix { - return new SVGMatrix( - multiplyMatrices(this, { a: 1, b: 0, c: 0, d: -1, e: 0, f: 0 }) - ); - } - - skewX(angle: number): SVGMatrix { - return new SVGMatrix( - multiplyMatrices(this, { - a: 1, - b: 0, - c: Math.tan(deg2rad * angle), - d: 1, - e: 0, - f: 0, - }) - ); - } - - skewY(angle: number): SVGMatrix { - return new SVGMatrix( - multiplyMatrices(this, { - a: 1, - b: Math.tan(deg2rad * angle), - c: 0, - d: 1, - e: 0, - f: 0, - }) - ); - } -} - -export function matrixTransform(matrix: Matrix, point: Point): Point { - const { a, b, c, d, e, f } = matrix; - const { x, y } = point; - return { - x: a * x + c * y + e, - y: b * x + d * y + f, - }; -} - -export class SVGPoint implements SVGPoint { - constructor(point?: Point) { - if (point) { - const { x, y } = point; - this.x = x; - this.y = y; - } else { - this.x = 0; - this.y = 0; - } - } - - matrixTransform(matrix: Matrix): SVGPoint { - return new SVGPoint(matrixTransform(matrix, this)); - } -} - -export const ownerSVGElement = { - createSVGPoint(): SVGPoint { - return new SVGPoint(); - }, - createSVGMatrix(): SVGMatrix { - return new SVGMatrix(); - }, -}; - -export default class Shape

extends Component { - [x: string]: unknown; - root: (Shape

& NativeMethods) | null = null; - constructor(props: Readonly

| P) { - super(props); - SvgTouchableMixin(this); - } - - refMethod: (instance: (Shape

& NativeMethods) | null) => void = ( - instance: (Shape

& NativeMethods) | null - ) => { - this.root = instance; - }; - - // Hack to make Animated work with Shape components. - getNativeScrollRef(): (Shape

& NativeMethods) | null { - return this.root; - } - - setNativeProps = ( - props: P & { - matrix?: ColumnMajorTransformMatrix; - fill?: ColorValue; - } & TransformProps - ) => { - if (props.fill) { - // @ts-ignore TODO: native `fill` prop differs from the one passed in props - props.fill = extractBrush(props.fill); - } - this.root?.setNativeProps(props); - }; - - /* - * The following native methods are experimental and likely broken in some - * ways. If you have a use case for these, please open an issue with a - * representative example / reproduction. - * */ - getBBox = (options?: SVGBoundingBoxOptions): SVGRect | undefined => { - const { - fill = true, - stroke = true, - markers = true, - clipped = true, - } = options || {}; - const handle = findNodeHandle(this.root); - const RNSVGRenderableModule = - require('react-native-svg/src/fabric/NativeSvgRenderableModule').default; - return RNSVGRenderableModule.getBBox(handle, { - fill, - stroke, - markers, - clipped, - }); - }; - - getCTM = (): SVGMatrix => { - const handle = findNodeHandle(this.root); - const RNSVGRenderableModule: Spec = - require('react-native-svg/src/fabric/NativeSvgRenderableModule').default; - return new SVGMatrix(RNSVGRenderableModule.getCTM(handle)); - }; - - getScreenCTM = (): SVGMatrix => { - const handle = findNodeHandle(this.root); - const RNSVGRenderableModule: Spec = - require('react-native-svg/src/fabric/NativeSvgRenderableModule').default; - return new SVGMatrix(RNSVGRenderableModule.getScreenCTM(handle)); - }; - - isPointInFill = (options: DOMPointInit): boolean | undefined => { - const handle = findNodeHandle(this.root); - const RNSVGRenderableModule: Spec = - require('react-native-svg/src/fabric/NativeSvgRenderableModule').default; - return RNSVGRenderableModule.isPointInFill(handle, options); - }; - - isPointInStroke = (options: DOMPointInit): boolean | undefined => { - const handle = findNodeHandle(this.root); - const RNSVGRenderableModule: Spec = - require('react-native-svg/src/fabric/NativeSvgRenderableModule').default; - return RNSVGRenderableModule.isPointInStroke(handle, options); - }; - - getTotalLength = (): number | undefined => { - const handle = findNodeHandle(this.root); - const RNSVGRenderableModule: Spec = - require('react-native-svg/src/fabric/NativeSvgRenderableModule').default; - return RNSVGRenderableModule.getTotalLength(handle); - }; - - getPointAtLength = (length: number): SVGPoint => { - const handle = findNodeHandle(this.root); - const RNSVGRenderableModule: Spec = - require('react-native-svg/src/fabric/NativeSvgRenderableModule').default; - return new SVGPoint( - RNSVGRenderableModule.getPointAtLength(handle, { length }) - ); - }; -} -Shape.prototype.ownerSVGElement = ownerSVGElement; diff --git a/react-native-harmony-svg/src/fabric/NativeSvgImageModule.ts b/react-native-harmony-svg/src/fabric/NativeSvgImageModule.ts deleted file mode 100644 index 026b0fc..0000000 --- a/react-native-harmony-svg/src/fabric/NativeSvgImageModule.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ -// its needed for codegen to work -import type { TurboModule } from 'react-native'; -import { TurboModuleRegistry } from 'react-native'; - -export interface Spec extends TurboModule { - getBase64String(uri: string): Promise; -} - -export default TurboModuleRegistry.get('RNSVGImageModule')!; diff --git a/tester/harmony/svg/src/main/cpp/CMakeLists.txt b/tester/harmony/svg/src/main/cpp/CMakeLists.txt index 3a697d8..7f389e0 100644 --- a/tester/harmony/svg/src/main/cpp/CMakeLists.txt +++ b/tester/harmony/svg/src/main/cpp/CMakeLists.txt @@ -11,11 +11,16 @@ file(GLOB rnoh_svg_SRC CONFIGURE_DEPENDS turboModules/*.cpp utils/*.cpp svgImage/*.cpp + downloadUtils/*.cpp ) add_library(rnoh_svg SHARED ${rnoh_svg_SRC} ${rnoh_svg_generated_SRC}) target_include_directories(rnoh_svg PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${rnoh_svg_generated_dir}) + target_link_libraries(rnoh_svg PUBLIC rnoh) target_link_libraries(rnoh_svg PUBLIC libimage_source.so) target_link_libraries(rnoh_svg PUBLIC libpixelmap.so) target_link_libraries(rnoh_svg PUBLIC libimage_packer.so) target_link_libraries(rnoh_svg PUBLIC librawfile.z.so) +target_link_libraries(rnoh_svg PUBLIC librcp_c.so) +target_link_libraries(rnoh_svg PUBLIC libohfileuri.so) +target_link_directories(rnoh_svg PUBLIC ${HMOS_SDK_NATIVE}/sysroot/usr/lib/aarch64-linux-ohos) diff --git a/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGBaseComponentInstance.h b/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGBaseComponentInstance.h index 007b59c..cc935ec 100644 --- a/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGBaseComponentInstance.h +++ b/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGBaseComponentInstance.h @@ -22,7 +22,6 @@ template class RNSVGBaseComponentInstance : public CppComponentInst void onPropsChanged(typename CppComponentInstance::SharedConcreteProps const &props) override { pointerEvents_ = props->pointerEvents.size() == 0 ? "auto" : props->pointerEvents; - svgMarkDirty(); } void onFinalizeUpdates() override { diff --git a/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGImageComponentInstance.cpp b/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGImageComponentInstance.cpp index 457f1f4..6fca558 100644 --- a/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGImageComponentInstance.cpp +++ b/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGImageComponentInstance.cpp @@ -5,12 +5,16 @@ */ #include "RNSVGImageComponentInstance.h" +#include "turboModules/RNSVGImageModule.h" namespace rnoh { namespace svg { RNSVGImageComponentInstance::RNSVGImageComponentInstance(Context context) - : RNSVGBaseComponentInstance(std::move(context)) { + : RNSVGBaseComponentInstance(std::move(context)), + ImageSourceResolver::ImageSourceUpdateListener( + (std::dynamic_pointer_cast(m_deps->rnInstance.lock()->getTurboModule("RNSVGImageModule"))) + ->getImageSourceResolver()) { SetSvgNode(m_svgImage); } @@ -29,8 +33,65 @@ void RNSVGImageComponentInstance::UpdateElementProps() { m_svgImage->SetHeight(m_props->height); m_svgImage->SetAlign(m_props->align); m_svgImage->SetMeetOrSlice(m_props->meetOrSlice); - m_svgImage->SetImageSource(m_props->src); + m_svgImage->SetImageSource(m_src); } +void RNSVGImageComponentInstance::onPropsChanged(SharedConcreteProps const &props) { + if (!m_props || m_props->src != props->src) { + if (!props->src.uri.empty() && props->src.uri.find("http", 0) != 0) { + // 非网络资源 + m_src.uri = props->src.uri; + return; + } + std::string cacheDir = FindLocalCacheByUri(props->src.uri); + if (!cacheDir.empty()) { + DLOG(INFO) << "[SVGImage] hit cache: " << cacheDir; + m_src.uri = cacheDir; + } + } +}; + +std::string RNSVGImageComponentInstance::FindLocalCacheByUri(std::string const &uri) { + // 非网络资源,直接返回uri + if (uri.find("http", 0) != 0) { + return uri; + } + + auto rnInstance = m_deps->rnInstance.lock(); + if (!rnInstance) { + return uri; + } + + auto turboModule = rnInstance->getTurboModule("RNSVGImageModule"); + if (!turboModule) { + return uri; + } + + auto arkTsTurboModule = std::dynamic_pointer_cast(turboModule); + if (!arkTsTurboModule || !arkTsTurboModule->getImageSourceResolver()) { + LOG(ERROR) << "[SVGImage] FastImageSourceResolver is unavailable!"; + return uri; + } + + // 查找缓存目录 + auto cacheDir = arkTsTurboModule->getImageSourceResolver()->resolveImageSources(*this, uri); + if (!cacheDir.empty()) { + return cacheDir; // 如果找到缓存文件,返回缓存路径 + } + // 否则开始下载 + arkTsTurboModule->downloadImage(uri, {}); + return ""; // 返回空字符串,表示下载未完成或缓存未命中 +} + +void RNSVGImageComponentInstance::onImageSourceCacheUpdate(std::string imageUri) { + LOG(INFO) << "[SVGImage] onImageSourceCacheUpdate, uri: " << imageUri; + m_src.uri = imageUri; + onFinalizeUpdates(); +}; + +void RNSVGImageComponentInstance::onImageSourceCacheDownloadFileFail() { + LOG(ERROR) << "[SVGImage] ImageSource download fail!"; +}; + } // namespace svg } // namespace rnoh diff --git a/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGImageComponentInstance.h b/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGImageComponentInstance.h index 3687a44..a264775 100644 --- a/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGImageComponentInstance.h +++ b/tester/harmony/svg/src/main/cpp/componentInstances/RNSVGImageComponentInstance.h @@ -9,19 +9,34 @@ #include "RNSVGBaseComponentInstance.h" #include "svgImage/RNSVGImageShadowNode.h" #include "SvgImage.h" +#include "downloadUtils/ImageSourceResolver.h" +#include "downloadUtils/HttpTaskProcessor.h" namespace rnoh { namespace svg { -class RNSVGImageComponentInstance : public RNSVGBaseComponentInstance { +class RNSVGImageComponentInstance : public RNSVGBaseComponentInstance, + public ImageSourceResolver::ImageSourceUpdateListener { public: explicit RNSVGImageComponentInstance(Context context); protected: void UpdateElementProps() override; + void onPropsChanged(SharedConcreteProps const &props) override; + + // used for find local cache of uri, if not find return uri + std::string FindLocalCacheByUri(std::string const &uri); + + // ImageSourceUpdateListener download success callback + void onImageSourceCacheUpdate(std::string imageUri) override; + + // ImageSourceUpdateListener download fail callback + void onImageSourceCacheDownloadFileFail() override; + private: std::shared_ptr m_svgImage = std::make_shared(); + facebook::react::ImageSource m_src; }; } // namespace svg diff --git a/tester/harmony/svg/src/main/cpp/downloadUtils/HttpTaskProcessor.cpp b/tester/harmony/svg/src/main/cpp/downloadUtils/HttpTaskProcessor.cpp new file mode 100644 index 0000000..7b1f174 --- /dev/null +++ b/tester/harmony/svg/src/main/cpp/downloadUtils/HttpTaskProcessor.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +#include +#include +#include "HttpTaskProcessor.h" + +namespace rnoh { +namespace svg { +std::vector HttpTaskProcessor::taskList_; +std::unordered_set HttpTaskProcessor::threadIds_; + +void ResponseCallback(void *usrCtx, Rcp_Response *response, uint32_t errCode) { + HttpTaskProcessor *processor = static_cast(usrCtx); + + // 记录当前线程的ID + std::ostringstream oss; + oss << std::this_thread::get_id(); + processor->pushThreadID(oss.str()); + + if (response != NULL) { + if (response->statusCode == RCP_OK) { + size_t status = processor->saveImage(response->body.buffer, response->body.length); + if (processor->instance_ && status != 0) { + processor->instance_->imageDownloadComplete(processor->uri_, processor->filePath_); + } else if (processor->instance_) { + LOG(WARNING) << "[SVGImage] ResponseCallback imageDownloadFail, status: " << status; + processor->instance_->imageDownloadFail(processor->uri_); + } + } + + response->destroyResponse(response); + } else { + LOG(ERROR) << "[SVGImage] Callback Response is NULL, errCode: " << errCode; + if (processor->instance_) { + processor->instance_->imageDownloadFail(processor->uri_); + } + } + + processor->markEnd(); +} + +void HttpTaskProcessor::createParentPath(const fs::path &filePath) { + fs::path parentPath = filePath.parent_path(); + if (!parentPath.empty() && !fs::exists(parentPath)) { + fs::create_directories(parentPath); + } +} + +size_t HttpTaskProcessor::saveImage(const char *buffer, uint32_t length) { + size_t status = 0; + if (filePath_ == "") + return status; + createParentPath(filePath_); + fp_ = fopen(filePath_.c_str(), "w"); + if (fp_) { + status = fwrite(buffer, sizeof(char), length, fp_); + fclose(fp_); + } else { + LOG(ERROR) << "[SVGImage] write to file failed: " << filePath_; + } + return status; +} + +void HttpTaskProcessor::markStart() { + finished_ = false; + auto now = std::chrono::high_resolution_clock::now(); + startTime_ = std::chrono::time_point_cast(now).time_since_epoch(); +} + +void HttpTaskProcessor::markEnd() { + // 记录时间戳,标记任务结束使得keep() 返回false + auto now = std::chrono::high_resolution_clock::now(); + endTime_ = std::chrono::time_point_cast(now).time_since_epoch(); + finished_ = true; +} + +bool HttpTaskProcessor::initializeRequestAndSession(const char *uri, + const std::map *headers) { + // 创建http request 对象 + request_ = HMS_Rcp_CreateRequest(uri); + if (request_ == nullptr) { + LOG(ERROR) << "[SVGImage] Failed to create HTTP request!"; + return false; + } + + // 如果有 headers 参数,初始化 headers + if (headers) { + Rcp_Headers *header = HMS_Rcp_CreateHeaders(); + for (const auto &it : *headers) { + if (auto errorCode = HMS_Rcp_SetHeaderValue(header, it.first.c_str(), it.second.c_str())) { + LOG(ERROR) << "[SVGImage] launchHttpRequestWithHeaders create headers error: " << errorCode; + return false; + } + } + request_->headers = header; + } + + // 创建http 会话对象 + uint32_t errorCode = 0; + session_ = HMS_Rcp_CreateSession(nullptr, &errorCode); + if (errorCode) { + LOG(ERROR) << "[SVGImage] Failed to create HTTP session, error code: " << errorCode; + return false; + } + + return true; +} + +void HttpTaskProcessor::setResponseCallback() { + // 设置响应数据处理的回调函数 + responseCallback_.callback = ResponseCallback; + responseCallback_.usrCtx = this; +} + +void HttpTaskProcessor::launchHttpRequest(const char *uri) { + markStart(); + this->uri_ = uri; + if (!initializeRequestAndSession(uri)) { + if (instance_) { + instance_->imageDownloadFail(uri); + } + markEnd(); + return; + } + + // 设置响应数据处理的回调函数 + setResponseCallback(); + + // API异步发起请求 + if (auto errCode = HMS_Rcp_Fetch(session_, request_, &responseCallback_)) { + LOG(ERROR) << "[SVGImage] launchHttpRequest HMS_Rcp_Fetch error: " << errCode; + if (instance_) { + instance_->imageDownloadFail(uri); + } + markEnd(); + } +} + +void HttpTaskProcessor::launchHttpRequestWithHeaders(const char *uri, + const std::map &headers) { + markStart(); + this->uri_ = uri; + if (!initializeRequestAndSession(uri, &headers)) { + if (instance_) { + instance_->imageDownloadFail(uri); + } + markEnd(); + return; + } + + // 设置响应数据处理的回调函数 + setResponseCallback(); + + // API异步发起请求 + if (auto errCode = HMS_Rcp_Fetch(session_, request_, &responseCallback_)) { + LOG(ERROR) << "[SVGImage] launchHttpRequest HMS_Rcp_Fetch error: " << errCode; + if (instance_) { + instance_->imageDownloadFail(uri); + } + markEnd(); + } +} + +// static +void HttpTaskProcessor::waitAllTask() { + bool wait = true; + while (wait) { + wait = false; + for (auto *processor : taskList_) { + if (processor->keep()) + wait = true; + } + if (wait) { + usleep(1000 * 1000); + echoThreadIds(); + } + } +} + +// static +void HttpTaskProcessor::destroyAllTask() { + for (auto *processor : taskList_) { + processor->echoSpendTime(); + delete processor; + } + + taskList_.clear(); + threadIds_.clear(); +} + +// static +void HttpTaskProcessor::echoThreadIds() { + for (auto id : threadIds_) { + DLOG(INFO) << "[SVGImage] NetDownload Task Thread ID: " << id.c_str(); + } + + DLOG(INFO) << "[SVGImage] NetDownload Number Of Thread : " << threadIds_.size(); +} + +void HttpTaskProcessor::pushThreadID(std::string id) { threadIds_.insert(id); } + +} // namespace svg +} // namespace rnoh \ No newline at end of file diff --git a/tester/harmony/svg/src/main/cpp/downloadUtils/HttpTaskProcessor.h b/tester/harmony/svg/src/main/cpp/downloadUtils/HttpTaskProcessor.h new file mode 100644 index 0000000..a7a0a3a --- /dev/null +++ b/tester/harmony/svg/src/main/cpp/downloadUtils/HttpTaskProcessor.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "RemoteCommunicationKit/rcp.h" +#include "ImageSourceResolver.h" +#include "utils/StringUtils.h" + +namespace fs = std::filesystem; +namespace rnoh { +namespace svg { +class HttpTaskProcessor { +public: + HttpTaskProcessor() { taskList_.push_back(this); } + + ~HttpTaskProcessor() { + if (request_ != nullptr) { + // 释放请求资源 + HMS_Rcp_DestroyRequest(request_); + request_ = nullptr; + } + } + + static void clearTaskList() { taskList_.clear(); } + + void createParentPath(const fs::path &filePath); + + size_t saveImage(const char *buffer, uint32_t length); + + void markStart(); + + void markEnd(); + + bool keep() { return !finished_; } + + void echoSpendTime() { + DLOG(INFO) << "[SVGImage] HttpTaskProcessor Spend Time(ns): " << endTime_.count() - startTime_.count(); + } + + bool initializeRequestAndSession(const char *uri, const std::map *headers = nullptr); + void setResponseCallback(); + + void launchHttpRequest(const char *uri); + void launchHttpRequestWithHeaders(const char *uri, const std::map &headers); + + + void pushThreadID(std::string id); + + static void waitAllTask(); + static void destroyAllTask(); + static void echoThreadIds(); + + std::shared_ptr instance_; + + std::string uri_; + std::string filePath_; + +private: + static std::vector taskList_; + static std::unordered_set threadIds_; + + FILE *fp_ = nullptr; + + std::chrono::nanoseconds startTime_; + std::chrono::nanoseconds endTime_; + + bool finished_ = false; + + Rcp_Request *request_ = nullptr; + Rcp_Session *session_ = nullptr; + + Rcp_ResponseCallbackObject responseCallback_; +}; + +} // namespace svg +} // namespace rnoh diff --git a/tester/harmony/svg/src/main/cpp/downloadUtils/ImageSourceResolver.cpp b/tester/harmony/svg/src/main/cpp/downloadUtils/ImageSourceResolver.cpp new file mode 100644 index 0000000..1ab270a --- /dev/null +++ b/tester/harmony/svg/src/main/cpp/downloadUtils/ImageSourceResolver.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +#include +#include "ImageSourceResolver.h" + +namespace rnoh { +namespace svg { + +std::string ImageSourceResolver::resolveImageSources(ImageSourceUpdateListener &listener, std::string uri) { + // 判断是否还在下载中 + if (m_pendingSet.find(uri) != m_pendingSet.end()) { + removeListener(&listener); + addListenerForURI(uri, &listener); + return ""; + } + // 生成图像的哈希缓存文件名 + auto hashedFileName = StringUtils::generateHashedFileName(uri); + + bool exist = remoteImageSourceMap_.get(hashedFileName); + if (exist) { + auto cacheUri = "file://" + fileCacheDir_ + hashedFileName; + return cacheUri; + } + removeListener(&listener); + addListenerForURI(uri, &listener); + return ""; +} + +void ImageSourceResolver::addListenerForURI(const std::string &uri, ImageSourceUpdateListener *listener) { + listener->observedUri = uri; + auto it = m_uriListenersMap.find(uri); + if (it == m_uriListenersMap.end()) { + m_uriListenersMap.emplace(uri, std::initializer_list{listener}); + return; + } + if (std::find(it->second.begin(), it->second.end(), listener) != it->second.end()) { + return; + } + it->second.push_back(listener); +} + +void ImageSourceResolver::removeListenerForURI(const std::string &uri, ImageSourceUpdateListener *listener) { + auto it = m_uriListenersMap.find(uri); + if (it == m_uriListenersMap.end()) { + return; + } + auto &listeners = it->second; + auto listenerPos = std::find(listeners.begin(), listeners.end(), listener); + if (listenerPos != listeners.end()) { + listeners.erase(listenerPos); + if (listeners.empty()) { + m_uriListenersMap.erase(uri); + } + } +} + +void ImageSourceResolver::removeListener(ImageSourceUpdateListener *listener) { + if (!listener->observedUri.empty()) { + removeListenerForURI(listener->observedUri, listener); + } +} + +void ImageSourceResolver::imageDownloadComplete(std::string uri, std::string fileUri) { + auto pend = m_pendingSet.find(uri); + if (pend != m_pendingSet.end()) { + m_pendingSet.erase(uri); + } + // 生成图像的哈希缓存文件名 + auto hashedFileName = StringUtils::generateHashedFileName(uri); + DLOG(INFO) << "[SVGImage] remoteImageSourceMap PUT: " << hashedFileName; + remoteImageSourceMap_.put(hashedFileName, true); + auto it = m_uriListenersMap.find(uri); + if (it == m_uriListenersMap.end()) { + return; + } + + auto &listeners = it->second; + for (auto listener : listeners) { + listener->onImageSourceCacheUpdate(fileUri); + removeListenerForURI(uri, listener); + } +} + +void ImageSourceResolver::imageDownloadFail(std::string uri) { + auto pend = m_pendingSet.find(uri); + if (pend != m_pendingSet.end()) { + m_pendingSet.erase(uri); + } + + auto it = m_uriListenersMap.find(uri); + if (it == m_uriListenersMap.end()) { + return; + } + auto &listeners = it->second; + for (auto listener : listeners) { + listener->onImageSourceCacheDownloadFileFail(); + removeListenerForURI(uri, listener); + } +} + +} // namespace svg +} // namespace rnoh \ No newline at end of file diff --git a/tester/harmony/svg/src/main/cpp/downloadUtils/ImageSourceResolver.h b/tester/harmony/svg/src/main/cpp/downloadUtils/ImageSourceResolver.h new file mode 100644 index 0000000..1711a68 --- /dev/null +++ b/tester/harmony/svg/src/main/cpp/downloadUtils/ImageSourceResolver.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +#pragma once + +#include +#include +#include +#include "RNOH/RNInstance.h" +#include "downloadUtils/LRUCache.h" +#include "utils/StringUtils.h" + +namespace fs = std::filesystem; + +namespace rnoh { +namespace svg { + +// ImageSourceResolver 类用于管理图像源的缓存与下载 +class ImageSourceResolver { +public: + using Shared = std::shared_ptr; + + // 构造函数,初始化缓存目录以及设置LRU缓存 + explicit ImageSourceResolver(std::string fileCacheDir) + : m_uriListenersMap(), fileCacheDir_(fileCacheDir), + remoteImageSourceMap_(128, [this](std::string fileUri, bool a) { + // 删除缓存文件(LRU缓存淘汰回调) + fs::path filePath = this->fileCacheDir_ + fileUri; + if (fs::exists(filePath) && fs::is_regular_file(filePath)) { + LOG(INFO) << "[SVGImage] remoteImageSourceMap delete: " << filePath; + return fs::remove(filePath); + } + return false; + }) {} + + // 图像源更新的监听器基类 + class ImageSourceUpdateListener { + public: + std::string observedUri; // 监听的图像URI + + ImageSourceUpdateListener(ImageSourceResolver::Shared const &ImageSourceResolver) + : m_imageSourceResolver(ImageSourceResolver) {} + + // 注销监听器 + ~ImageSourceUpdateListener() { + if (!m_imageSourceResolver) { + m_imageSourceResolver->removeListener(this); + } + } + + // 监听图像缓存更新 + virtual void onImageSourceCacheUpdate(std::string imageUri) = 0; + + // 监听图像下载失败 + virtual void onImageSourceCacheDownloadFileFail() = 0; + + private: + ImageSourceResolver::Shared const &m_imageSourceResolver; + }; + + // 解析图像源的缓存路径 + std::string resolveImageSources(ImageSourceUpdateListener &listener, std::string uri); + + // 为URI添加监听器 + void addListenerForURI(const std::string &uri, ImageSourceUpdateListener *listener); + + // 从URI移除监听器 + void removeListenerForURI(const std::string &uri, ImageSourceUpdateListener *listener); + + // 从解析器中移除监听器 + void removeListener(ImageSourceUpdateListener *listener); + + // 下载完成后更新图像缓存 + void imageDownloadComplete(std::string uri, std::string fileUri); + + // 下载失败时,通知图像源更新失败 + void imageDownloadFail(std::string uri); + + // 使用LRU缓存来管理磁盘文件路径与URI的映射 + LRUCache remoteImageSourceMap_; + + // 存储图像缓存目录路径 + std::string fileCacheDir_; + +private: + // 存储URI与对应的监听器(支持多个监听器) + std::unordered_map> m_uriListenersMap; + + // 存储正在下载的图像URI集合 + std::unordered_set m_pendingSet; +}; + +} // namespace svg +} // namespace rnoh diff --git a/tester/harmony/svg/src/main/cpp/downloadUtils/LRUCache.h b/tester/harmony/svg/src/main/cpp/downloadUtils/LRUCache.h new file mode 100644 index 0000000..a2dcce5 --- /dev/null +++ b/tester/harmony/svg/src/main/cpp/downloadUtils/LRUCache.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +#pragma once + +#include +#include +#include + +namespace rnoh { +namespace svg { +template class LRUCache { +private: + std::unordered_map::iterator>> m_cache; + std::list m_lruList; + std::mutex m_mtx; // 用于同步的互斥锁 + std::function m_deleteCallback; + int m_cacheCapacity; // 成员变量的容量 + +public: + // 构造函数,允许设置自定义容量和删除回调函数 + LRUCache(int customCapacity = defaultCapacity, std::function callback = nullptr) + : m_cacheCapacity(customCapacity), m_deleteCallback(callback) {} + + ValueType get(const KeyType &key) { + std::lock_guard lock(m_mtx); + if (m_cache.find(key) != m_cache.end()) { + m_lruList.erase(m_cache[key].second); + m_lruList.push_front(key); + m_cache[key].second = m_lruList.begin(); + return m_cache[key].first; + } + return ValueType(); // 如果没找到,返回默认构造的值 + } + + void put(const KeyType &key, const ValueType &value) { + std::lock_guard lock(m_mtx); + if (m_cache.find(key) != m_cache.end()) { + m_cache[key].first = value; + m_lruList.erase(m_cache[key].second); + m_lruList.push_front(key); + m_cache[key].second = m_lruList.begin(); + } else { + if (m_cache.size() >= m_cacheCapacity) { + KeyType lruKey = m_lruList.back(); + + // 调用删除回调函数 + if (m_deleteCallback) { + m_deleteCallback(lruKey, m_cache[lruKey].first); + } + + m_cache.erase(lruKey); + m_lruList.pop_back(); + } + m_lruList.push_front(key); + m_cache[key] = std::make_pair(value, m_lruList.begin()); + } + } + + bool remove(const KeyType &key) { + std::lock_guard lock(m_mtx); + bool status = false; + if (m_cache.find(key) != m_cache.end()) { + ValueType value = m_cache[key].first; + m_lruList.erase(m_cache[key].second); + if (m_deleteCallback) { + status = m_deleteCallback(key, value); + } + + m_cache.erase(key); + } + return status; + } + bool clearAll() { + std::lock_guard lock(m_mtx); + // 从后向前遍历 lruList + for (auto it = m_lruList.end(); it != m_lruList.begin();) { + --it; // 先向前移动迭代器 + KeyType key = *it; // 获取当前键 + if (m_deleteCallback) { + // 调用删除回调并检查返回值 + if (!m_deleteCallback(key, m_cache[key].first)) { + return false; // 如果删除失败,返回 false + } + } + m_cache.erase(key); // 从 cache 中删除该键 + it = m_lruList.erase(it); // 删除当前节点并更新迭代器 + } + return true; // 成功删除所有元素,返回 true + } + size_t size() { return m_cache.size(); } +}; + +} // namespace svg +} // namespace rnoh \ No newline at end of file diff --git a/tester/harmony/svg/src/main/cpp/turboModules/RNSVGImageModule.cpp b/tester/harmony/svg/src/main/cpp/turboModules/RNSVGImageModule.cpp index e5a9c07..c46d415 100644 --- a/tester/harmony/svg/src/main/cpp/turboModules/RNSVGImageModule.cpp +++ b/tester/harmony/svg/src/main/cpp/turboModules/RNSVGImageModule.cpp @@ -5,13 +5,50 @@ */ #include "RNSVGImageModule.h" +#include "utils/StringUtils.h" -using namespace rnoh; using namespace facebook; +namespace rnoh { +namespace svg { RNSVGImageModule::RNSVGImageModule(const ArkTSTurboModule::Context ctx, const std::string name) : ArkTSTurboModule(ctx, name) { methodMap_ = { - ARK_ASYNC_METHOD_METADATA(getBase64String, 1), + ARK_ASYNC_METHOD_METADATA(getCacheDir, 0), }; -} \ No newline at end of file + + // 获取缓存路径前缀 + auto cache = this->callSync("getCacheDir", {}); + m_fileCacheDir = cache.asString() + "/"; + m_imageSourceResolver = std::make_shared(m_fileCacheDir); + + fs::path directoryPath = m_fileCacheDir; + // 将已有的缓存文件名放入remoteImageSourceMap + if (fs::exists(directoryPath) && fs::is_directory(directoryPath)) { + for (const auto &entry : fs::directory_iterator(directoryPath)) { + if (fs::is_regular_file(entry.path())) { + m_imageSourceResolver->remoteImageSourceMap_.put(entry.path().filename(), true); + } + } + } +} + +/** + * + * @param uri 图片uri + * @param headers 请求头 + */ +void RNSVGImageModule::downloadImage(const std::string &uri, const std::map &headers) { + HttpTaskProcessor *processor = new HttpTaskProcessor(); + processor->instance_ = m_imageSourceResolver; + processor->filePath_ = m_fileCacheDir + StringUtils::generateHashedFileName(uri); + + if (headers.empty()) { + processor->launchHttpRequest(uri.c_str()); + } else { + processor->launchHttpRequestWithHeaders(uri.c_str(), headers); + } +} + +} // namespace svg +} // namespace rnoh diff --git a/tester/harmony/svg/src/main/cpp/turboModules/RNSVGImageModule.h b/tester/harmony/svg/src/main/cpp/turboModules/RNSVGImageModule.h index 278b3ba..4ec516b 100644 --- a/tester/harmony/svg/src/main/cpp/turboModules/RNSVGImageModule.h +++ b/tester/harmony/svg/src/main/cpp/turboModules/RNSVGImageModule.h @@ -8,12 +8,25 @@ #include #include "RNOH/ArkTSTurboModule.h" +#include "downloadUtils/ImageSourceResolver.h" +#include "downloadUtils/HttpTaskProcessor.h" namespace rnoh { +namespace svg { class JSI_EXPORT RNSVGImageModule : public ArkTSTurboModule { public: RNSVGImageModule(const ArkTSTurboModule::Context ctx, const std::string name); -}; + ~RNSVGImageModule() { HttpTaskProcessor::destroyAllTask(); } + + void downloadImage(const std::string &uri, const std::map &headers); + + std::shared_ptr getImageSourceResolver() { return m_imageSourceResolver; } + +private: + std::string m_fileCacheDir; + std::shared_ptr m_imageSourceResolver = nullptr; +}; +} // namespace svg } // namespace rnoh diff --git a/tester/harmony/svg/src/main/cpp/utils/StringUtils.h b/tester/harmony/svg/src/main/cpp/utils/StringUtils.h index a1df33d..89818a6 100644 --- a/tester/harmony/svg/src/main/cpp/utils/StringUtils.h +++ b/tester/harmony/svg/src/main/cpp/utils/StringUtils.h @@ -235,7 +235,7 @@ static Dimension FromString(const std::string &str) { }; static Dimension StringToDimensionWithUnit(const std::string &value, DimensionUnit defaultUnit = DimensionUnit::PX, - float defaultValue = 0.0f, bool isCalc = false) { + float defaultValue = 0.0f, bool isCalc = false) { errno = 0; if (std::strcmp(value.c_str(), "auto") == 0) { return Dimension(defaultValue, DimensionUnit::AUTO); @@ -277,8 +277,8 @@ inline Dimension StringToDimension(const std::string &value, bool useVp = false) } static bool StringToDimensionWithUnitNG(const std::string &value, Dimension &dimensionResult, - DimensionUnit defaultUnit = DimensionUnit::PX, float defaultValue = 0.0f, - bool isCalc = false) { + DimensionUnit defaultUnit = DimensionUnit::PX, float defaultValue = 0.0f, + bool isCalc = false) { errno = 0; if (std::strcmp(value.c_str(), "auto") == 0) { dimensionResult = Dimension(defaultValue, DimensionUnit::AUTO); @@ -591,7 +591,8 @@ inline void TransformStrCase(std::string &str, int32_t textCase) { bool IsAscii(const std::string &str); -inline std::vector stringVectorToDoubleVector(const std::vector &stringVec, const double pixelDensity) { +inline std::vector stringVectorToDoubleVector(const std::vector &stringVec, + const double pixelDensity) { std::vector doubleVec; doubleVec.reserve(stringVec.size()); // 预分配内存以提高效率 for (const std::string &str : stringVec) { @@ -608,9 +609,36 @@ inline std::string doubleVectorToString(const std::vector &doubleVec) { } return oss.str(); // 将流中的内容转换为字符串并返回 } + std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len, bool url = false); std::string bitmapToBase64(OH_Drawing_Bitmap *bitmap); -} // namespace StringUtils +inline std::string getFileExtension(const std::string &uri) { + size_t pos = uri.rfind('.'); + if (pos != std::string::npos) { + return uri.substr(pos); // 返回后缀名,包括点 ".jpg" + } + return ""; // 如果没有扩展名,返回空字符串 +} + +inline std::string generateHash(const std::string &url) { + std::hash hash_fn; + size_t hash = hash_fn(url); + // 将哈希值转换为固定宽度的十六进制字符串(16位) + std::stringstream ss; + ss << std::hex << std::setw(16) << std::setfill('0') << hash; + return ss.str(); +} + +inline std::string generateHashedFileName(const std::string &uri) { + // 获取文件后缀名 + std::string extension = StringUtils::getFileExtension(uri); + // 对URI进行哈希 + std::string hash = StringUtils::generateHash(uri); + // 返回哈希值 + 文件后缀 + return hash + extension; +} + +} // namespace StringUtils } // namespace svg } // namespace rnoh diff --git a/tester/harmony/svg/src/main/ets/Logger.ts b/tester/harmony/svg/src/main/ets/Logger.ts deleted file mode 100644 index f747a60..0000000 --- a/tester/harmony/svg/src/main/ets/Logger.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved - * Use of this source code is governed by a MIT license that can be - * found in the LICENSE file. - */ - -import hilog from '@ohos.hilog'; - -class Logger { - private domain: number; - private prefix: string; - private format: string = '%{public}s, %{public}s'; - private isDebug: boolean; - - /** - * constructor. - * - * @param Prefix Identifies the log tag. - * @param domain Domain Indicates the service domain, which is a hexadecimal integer ranging from 0x0 to 0xFFFFF. - */ - constructor(prefix: string = 'MyApp', domain: number = 0xFF00, isDebug = false) { - this.prefix = prefix; - this.domain = domain; - this.isDebug = isDebug; - } - - debug(...args: string[]): void { - if(this.isDebug) { - hilog.debug(this.domain, this.prefix, this.format, args); - } - } - - info(...args: string[]): void { - hilog.info(this.domain, this.prefix, this.format, args); - } - - warn(...args: string[]): void { - hilog.warn(this.domain, this.prefix, this.format, args); - } - - error(...args: string[]): void { - hilog.error(this.domain, this.prefix, this.format, args); - } -} - -export default new Logger('RNSVG', 0xFF00, false) \ No newline at end of file diff --git a/tester/harmony/svg/src/main/ets/RNSVGImageModule.ts b/tester/harmony/svg/src/main/ets/RNSVGImageModule.ts index dad951b..9bc8ebb 100644 --- a/tester/harmony/svg/src/main/ets/RNSVGImageModule.ts +++ b/tester/harmony/svg/src/main/ets/RNSVGImageModule.ts @@ -5,84 +5,11 @@ */ import { TurboModule } from "@rnoh/react-native-openharmony/ts"; -import { BusinessError } from '@ohos.base'; -import image from '@ohos.multimedia.image'; -import http from '@ohos.net.http'; -import ResponseCode from '@ohos.net.http'; -import buffer from '@ohos.buffer'; -import Logger from './Logger' export class RNSVGImageModule extends TurboModule { - pixelMap: image.PixelMap | undefined = undefined - sourceType: string = 'image/png' - formatList: Map = new Map([ - ['image/jpeg', 'image/jpeg'], - ['image/png', 'image/png'], - ['image/webp', 'image/webp'], - ]) - public static readonly NAME = 'RNSVGImageModule'; - async getBase64String(uri: string): Promise { - if (uri.startsWith('data:')) { - return Promise.resolve(uri); - } - let image: image.PixelMap | undefined = undefined; - try { - if (uri.includes('http')) { - image = await this.getPixelMapFromURL(uri); - } else { - //TODO get pixelMap from local uri - } - if (image === undefined) { - let message = "Error! Failed to decode Bitmap, uri: " + uri; - Logger.warn(message); - return Promise.reject(message); - } else { - return Promise.resolve(this.pixelMapToBase64(image)); - } - } catch (error) { - let message = "Error! Failed to decode pixelMap: " + error.message; - Logger.warn(message); - Promise.reject(message); - } - } - - async getPixelMapFromURL(src: string): Promise { - let pixelMap: image.PixelMap | undefined = undefined; - let data = await http.createHttp().request(src); - if (data.responseCode == ResponseCode.ResponseCode.OK && data.result instanceof ArrayBuffer) { - let imageData: ArrayBuffer = data.result; - if (data.header.hasOwnProperty('content-type')) { - this.sourceType = data.header['content-type']; - } - let imageSource: image.ImageSource = image.createImageSource(imageData); - let imageInfo = await imageSource.getImageInfo(); - let imageWidth = Math.round(imageInfo.size.width); - let imageHeight = Math.round(imageInfo.size.height); - let options: image.InitializationOptions = { - alphaType: 1, - editable: false, - pixelFormat: 3, - scaleMode: 1, - size: { width: imageWidth, height: imageHeight } - }; - pixelMap = await imageSource.createPixelMap(options); - } - return pixelMap; - } - - async pixelMapToBase64(pixelMap: image.PixelMap): Promise { - let base64Url: string = ''; - let imagePacker = image.createImagePacker(); - let packFormat: string = this.formatList.get(this.sourceType) ?? 'image/png'; - let packOptions: image.PackingOption = { format: packFormat, quality: 100 }; - let data: ArrayBuffer = await imagePacker.packing(pixelMap, packOptions); - if (data) { - let buf: buffer.Buffer = buffer.from(data); - base64Url = buf.toString('base64', 0, buf.length); - Logger.debug(`svgImage base64: data:${packFormat};base64,${base64Url}`); - } - return "data:" + packFormat + ";base64," + base64Url; + getCacheDir() { + return this.ctx.uiAbilityContext.cacheDir; } -} \ No newline at end of file +}