1 /*
2 * Copyright (c) 2025-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "pointer_renderer.h"
17
18 #include <regex>
19 #include "image_source.h"
20 #include "window_info.h"
21 #include "util.h"
22
23 #undef MMI_LOG_DOMAIN
24 #define MMI_LOG_DOMAIN MMI_LOG_CURSOR
25 #undef MMI_LOG_TAG
26 #define MMI_LOG_TAG "PointerRenderer"
27
28 constexpr uint32_t RENDER_STRIDE{4};
29 constexpr int32_t DEVCIE_INDEPENDENT_PIXELS{40};
30 constexpr float INCREASE_RATIO{1.22f};
31 constexpr int32_t MIN_POINTER_COLOR{0x000000};
32 constexpr int32_t MAX_POINTER_COLOR{0xFFFFFF};
33 constexpr int32_t OTHER_POINTER_COLOR{0x171717};
34 constexpr float CALCULATE_IMAGE_MIDDLE{2.0f};
35 constexpr uint32_t FOCUS_POINT{256};
36 constexpr float CALCULATE_MOUSE_ICON_BIAS{ 5.0f / 33.0f };
37 constexpr float ROTATION_ANGLE90 {90.0f};
38 constexpr float MAX_DPI {1.93f}; //1.93 is the max dpi for hardware cursor pointer to limit the image size within 256
39 constexpr uint32_t MAX_POINTER_SIZE {7};
40 const std::string IMAGE_POINTER_DEFAULT_PATH = "/system/etc/multimodalinput/mouse_icon/";
41
42 namespace OHOS::MMI {
43
GetImageSize() const44 int32_t RenderConfig::GetImageSize() const
45 {
46 float increaseRatio = AdjustIncreaseRatio(dpi);
47 return pow(increaseRatio, size - 1) * dpi * DEVCIE_INDEPENDENT_PIXELS;
48 }
49
AdjustIncreaseRatio(float originDpi) const50 float RenderConfig::AdjustIncreaseRatio(float originDpi) const
51 {
52 if (originDpi == 0.0f) {
53 MMI_HILOGE("The originDpi cannot be 0, return default increase ratio");
54 return INCREASE_RATIO;
55 }
56 if (originDpi <= MAX_DPI) {
57 return INCREASE_RATIO;
58 } else {
59 return INCREASE_RATIO * pow(MAX_DPI / originDpi, float(1.0f / float(MAX_POINTER_SIZE - 1)));
60 }
61 }
62
ToString() const63 std::string RenderConfig::ToString() const
64 {
65 std::ostringstream oss;
66 oss << "{style=" << style_ << ", align=" << align_ << ", color=" << color
67 << ", size=" << size << ", direction=" << direction
68 <<", dpi=" << dpi
69 << ", isHard=" << isHard << ", ImageSize=" << GetImageSize() << "}";
70 return oss.str();
71 }
72
GetOffsetX() const73 int32_t RenderConfig::GetOffsetX() const
74 {
75 int32_t width = this->GetImageSize();
76 switch (this->align_) {
77 case ANGLE_E:
78 return FOCUS_POINT;
79 case ANGLE_S: // fall-though
80 case ANGLE_N: // fall-though
81 case ANGLE_CENTER: // fall-though
82 return static_cast<int32_t>(FOCUS_POINT - width / CALCULATE_IMAGE_MIDDLE);
83 case ANGLE_W: // fall-though
84 case ANGLE_SE: // fall-though
85 case ANGLE_NE: // fall-though
86 return FOCUS_POINT - width;
87 case ANGLE_SW:
88 return FOCUS_POINT;
89 case ANGLE_NW:
90 return FOCUS_POINT - this->userIconHotSpotX;
91 case ANGLE_NW_RIGHT:
92 return FOCUS_POINT - width * CALCULATE_MOUSE_ICON_BIAS;
93 default:
94 MMI_HILOGW("No need calculate physicalX offset");
95 return FOCUS_POINT;
96 }
97 }
98
GetOffsetY() const99 int32_t RenderConfig::GetOffsetY() const
100 {
101 int32_t height = this->GetImageSize();
102 switch (this->align_) {
103 case ANGLE_E:
104 return FOCUS_POINT - height / CALCULATE_IMAGE_MIDDLE;
105 case ANGLE_S:
106 return FOCUS_POINT;
107 case ANGLE_W:
108 return FOCUS_POINT - height;
109 case ANGLE_N:
110 return FOCUS_POINT - height;
111 case ANGLE_SE:
112 return FOCUS_POINT - height;
113 case ANGLE_NE:
114 return FOCUS_POINT;
115 case ANGLE_SW:
116 return FOCUS_POINT - height;
117 case ANGLE_NW:
118 return FOCUS_POINT - this->userIconHotSpotY;
119 case ANGLE_CENTER:
120 return FOCUS_POINT - height / CALCULATE_IMAGE_MIDDLE;
121 case ANGLE_NW_RIGHT:
122 return FOCUS_POINT;
123 default:
124 MMI_HILOGW("No need calculate physicalY offset");
125 return FOCUS_POINT;
126 }
127 }
128
UserIconScale(uint32_t width,uint32_t height,const RenderConfig & cfg)129 image_ptr_t PointerRenderer::UserIconScale(uint32_t width, uint32_t height, const RenderConfig &cfg)
130 {
131 image_ptr_t image = nullptr;
132 image = ExtractDrawingImage(cfg.userIconPixelMap);
133 return image;
134 }
135
Render(uint8_t * addr,uint32_t width,uint32_t height,const RenderConfig & cfg)136 int32_t PointerRenderer::Render(uint8_t *addr, uint32_t width, uint32_t height, const RenderConfig &cfg)
137 {
138 CHKPR(addr, RET_ERR);
139 MMI_HILOGD("Render cfg:%{private}s, width:%{public}u, height:%{public}u", cfg.ToString().c_str(), width, height);
140
141 uint32_t addrSize = width * height * RENDER_STRIDE;
142 if (cfg.style_ == MOUSE_ICON::TRANSPARENT_ICON) {
143 (void)memset_s(addr, addrSize, 0, addrSize);
144 return RET_OK;
145 }
146
147 // construct bitmap
148 OHOS::Rosen::Drawing::Bitmap bitmap;
149 OHOS::Rosen::Drawing::BitmapFormat format {
150 OHOS::Rosen::Drawing::COLORTYPE_RGBA_8888,
151 OHOS::Rosen::Drawing::ALPHATYPE_OPAQUE,
152 };
153 if (!bitmap.Build(width, height, format)) {
154 MMI_HILOGE("Bitmap build failed");
155 }
156
157 // construct canvas and bind to bitmap
158 OHOS::Rosen::Drawing::Canvas canvas;
159 canvas.Bind(bitmap);
160 canvas.Clear(OHOS::Rosen::Drawing::Color::COLOR_TRANSPARENT);
161 if (cfg.direction) {
162 int32_t directionFlag = -1;
163 int32_t degree = static_cast<int32_t>(directionFlag * static_cast<int32_t>(cfg.direction) * ROTATION_ANGLE90);
164 canvas.Rotate(degree, FOCUS_POINT, FOCUS_POINT);
165 }
166 image_ptr_t image = nullptr;
167 if (cfg.userIconPixelMap == nullptr) {
168 image = LoadPointerImage(cfg);
169 } else {
170 image = UserIconScale(width, height, cfg);
171 }
172 CHKPR(image, RET_ERR);
173 // Draw image on canvas
174 canvas.DrawImage(*image, cfg.GetOffsetX(), cfg.GetOffsetY(), Rosen::Drawing::SamplingOptions());
175
176 errno_t ret = memcpy_s(addr, addrSize, bitmap.GetPixels(), bitmap.ComputeByteSize());
177 if (ret != EOK) {
178 MMI_HILOGE("Memcpy_s failed");
179 return RET_ERR;
180 }
181 return RET_OK;
182 }
183
LoadPointerImage(const RenderConfig & cfg)184 image_ptr_t PointerRenderer::LoadPointerImage(const RenderConfig &cfg)
185 {
186 auto pixelmap = LoadCursorSvgWithColor(cfg);
187 return ExtractDrawingImage(pixelmap);
188 }
189
ChangeSvgCursorColor(std::string & str,int32_t color)190 static void ChangeSvgCursorColor(std::string& str, int32_t color)
191 {
192 std::string targetColor = IntToHexRGB(color);
193 StringReplace(str, "#000000", targetColor);
194 if (color == MAX_POINTER_COLOR) {
195 // stroke=\"#FFFFFF" fill="#000000" stroke-linejoin="round" transform="xxx"
196 std::regex re("(<path.*)(stroke=\"#[a-fA-F0-9]{6}\")(.*path>)");
197 str = std::regex_replace(str, re, "$1stroke=\"#000000\"$3");
198 }
199 }
200
SetCursorColorBaseOnStyle(const RenderConfig & cfg,OHOS::Media::DecodeOptions & decodeOpts)201 void SetCursorColorBaseOnStyle(const RenderConfig &cfg, OHOS::Media::DecodeOptions &decodeOpts)
202 {
203 const bool isHandColor =
204 (cfg.style_ == HAND_GRABBING) || (cfg.style_ == HAND_OPEN) || (cfg.style_ == HAND_POINTING);
205 if (!isHandColor) {
206 return;
207 }
208
209 if ((cfg.color == MAX_POINTER_COLOR) || (cfg.color == MIN_POINTER_COLOR) || (cfg.color == OTHER_POINTER_COLOR)) {
210 decodeOpts.SVGOpts.fillColor = {.isValidColor = true, .color = MAX_POINTER_COLOR};
211 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MIN_POINTER_COLOR};
212 return;
213 }
214
215 decodeOpts.SVGOpts.fillColor = {.isValidColor = true, .color = cfg.color};
216 if (cfg.color == MAX_POINTER_COLOR) {
217 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MIN_POINTER_COLOR};
218 } else {
219 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MAX_POINTER_COLOR};
220 }
221 }
222
LoadCursorSvgWithColor(const RenderConfig & cfg)223 pixelmap_ptr_t PointerRenderer::LoadCursorSvgWithColor(const RenderConfig &cfg)
224 {
225 std::string svgContent;
226 if (!ReadFile(cfg.path_, svgContent)) {
227 MMI_HILOGE("read file failed");
228 return nullptr;
229 }
230
231 const bool isPartColor = (cfg.style_ == CURSOR_COPY) || (cfg.style_ == CURSOR_FORBID) || (cfg.style_ == HELP);
232 if (isPartColor) {
233 ChangeSvgCursorColor(svgContent, cfg.color);
234 }
235 OHOS::Media::SourceOptions opts;
236 std::unique_ptr<std::istream> isp(std::make_unique<std::istringstream>(svgContent));
237 uint32_t ret = 0;
238 auto imageSource = OHOS::Media::ImageSource::CreateImageSource(std::move(isp), opts, ret);
239 if (!imageSource || ret != ERR_OK) {
240 MMI_HILOGE("Get ImageSource failed, ret=%{public}u", ret);
241 }
242 CHKPP(imageSource);
243
244 int32_t imgSize = cfg.GetImageSize();
245 OHOS::Media::DecodeOptions decodeOpts;
246 decodeOpts.desiredSize = {.width = imgSize, .height = imgSize};
247 if (!isPartColor) {
248 decodeOpts.SVGOpts.fillColor = {.isValidColor = true, .color = cfg.color};
249 if (cfg.color == MAX_POINTER_COLOR) {
250 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MIN_POINTER_COLOR};
251 } else {
252 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MAX_POINTER_COLOR};
253 }
254 SetCursorColorBaseOnStyle(cfg, decodeOpts);
255 }
256
257 pixelmap_ptr_t pmap = imageSource->CreatePixelMap(decodeOpts, ret);
258 if (ret != ERR_OK) {
259 MMI_HILOGE("CreatePixelMap failed, ret=%{public}u", ret);
260 }
261 return pmap;
262 }
263
264 class PixelMapContext {
265 public:
PixelMapContext(pixelmap_ptr_t pixelMap)266 explicit PixelMapContext(pixelmap_ptr_t pixelMap) : pixelMap_(pixelMap) {}
~PixelMapContext()267 ~PixelMapContext()
268 {
269 pixelMap_ = nullptr;
270 }
271
272 private:
273 pixelmap_ptr_t pixelMap_{nullptr};
274 };
275
PixelMapReleaseProc(const void *,void * context)276 static void PixelMapReleaseProc(const void * /* pixels */, void *context)
277 {
278 PixelMapContext *ctx = static_cast<PixelMapContext *>(context);
279 if (ctx != nullptr) {
280 delete ctx;
281 }
282 }
283
284
PixelFormatToColorType(Media::PixelFormat pixelFormat)285 static Rosen::Drawing::ColorType PixelFormatToColorType(Media::PixelFormat pixelFormat)
286 {
287 switch (pixelFormat) {
288 case Media::PixelFormat::RGB_565:
289 return Rosen::Drawing::ColorType::COLORTYPE_RGB_565;
290 case Media::PixelFormat::RGBA_8888:
291 return Rosen::Drawing::ColorType::COLORTYPE_RGBA_8888;
292 case Media::PixelFormat::BGRA_8888:
293 return Rosen::Drawing::ColorType::COLORTYPE_BGRA_8888;
294 case Media::PixelFormat::ALPHA_8:
295 return Rosen::Drawing::ColorType::COLORTYPE_ALPHA_8;
296 case Media::PixelFormat::RGBA_F16:
297 return Rosen::Drawing::ColorType::COLORTYPE_RGBA_F16;
298 case Media::PixelFormat::UNKNOWN: // fall-though
299 case Media::PixelFormat::ARGB_8888: // fall-though
300 case Media::PixelFormat::RGB_888: // fall-though
301 case Media::PixelFormat::NV21: // fall-though
302 case Media::PixelFormat::NV12: // fall-though
303 case Media::PixelFormat::CMYK: // fall-though
304 default:
305 return Rosen::Drawing::ColorType::COLORTYPE_UNKNOWN;
306 }
307 }
308
AlphaTypeToAlphaType(Media::AlphaType alphaType)309 static Rosen::Drawing::AlphaType AlphaTypeToAlphaType(Media::AlphaType alphaType)
310 {
311 switch (alphaType) {
312 case Media::AlphaType::IMAGE_ALPHA_TYPE_UNKNOWN:
313 return Rosen::Drawing::AlphaType::ALPHATYPE_UNKNOWN;
314 case Media::AlphaType::IMAGE_ALPHA_TYPE_OPAQUE:
315 return Rosen::Drawing::AlphaType::ALPHATYPE_OPAQUE;
316 case Media::AlphaType::IMAGE_ALPHA_TYPE_PREMUL:
317 return Rosen::Drawing::AlphaType::ALPHATYPE_PREMUL;
318 case Media::AlphaType::IMAGE_ALPHA_TYPE_UNPREMUL:
319 return Rosen::Drawing::AlphaType::ALPHATYPE_UNPREMUL;
320 default:
321 return Rosen::Drawing::AlphaType::ALPHATYPE_UNKNOWN;
322 }
323 }
324
ConvertToColorSpace(Media::ColorSpace colorSpace)325 static std::shared_ptr<Rosen::Drawing::ColorSpace> ConvertToColorSpace(Media::ColorSpace colorSpace)
326 {
327 switch (colorSpace) {
328 case Media::ColorSpace::DISPLAY_P3:
329 return Rosen::Drawing::ColorSpace::CreateRGB(
330 Rosen::Drawing::CMSTransferFuncType::SRGB, Rosen::Drawing::CMSMatrixType::DCIP3);
331 case Media::ColorSpace::LINEAR_SRGB:
332 return Rosen::Drawing::ColorSpace::CreateSRGBLinear();
333 case Media::ColorSpace::SRGB:
334 return Rosen::Drawing::ColorSpace::CreateSRGB();
335 default:
336 return Rosen::Drawing::ColorSpace::CreateSRGB();
337 }
338 }
339
ExtractDrawingImage(pixelmap_ptr_t pixelMap)340 image_ptr_t PointerRenderer::ExtractDrawingImage(pixelmap_ptr_t pixelMap)
341 {
342 CHKPP(pixelMap);
343 Media::ImageInfo imageInfo;
344 pixelMap->GetImageInfo(imageInfo);
345 Rosen::Drawing::ImageInfo drawingImageInfo {
346 imageInfo.size.width,
347 imageInfo.size.height,
348 PixelFormatToColorType(imageInfo.pixelFormat),
349 AlphaTypeToAlphaType(imageInfo.alphaType),
350 ConvertToColorSpace(imageInfo.colorSpace),
351 };
352 Rosen::Drawing::Pixmap imagePixmap(drawingImageInfo, reinterpret_cast<const void*>(pixelMap->GetPixels()),
353 pixelMap->GetRowStride());
354 PixelMapContext *releaseContext = new (std::nothrow) PixelMapContext(pixelMap);
355 CHKPP(releaseContext);
356 auto image = Rosen::Drawing::Image::MakeFromRaster(imagePixmap, PixelMapReleaseProc, releaseContext);
357 if (image == nullptr) {
358 MMI_HILOGE("ExtractDrawingImage image fail");
359 delete releaseContext;
360 releaseContext = nullptr;
361 }
362 return image;
363 }
364
DrawImage(OHOS::Rosen::Drawing::Canvas & canvas,const RenderConfig & cfg)365 int32_t PointerRenderer::DrawImage(OHOS::Rosen::Drawing::Canvas &canvas, const RenderConfig &cfg)
366 {
367 if (cfg.style_ == MOUSE_ICON::LOADING) {
368 auto loadingImg = FindImg(cfg);
369 if (loadingImg == nullptr) {
370 loadingImg = LoadPointerImage(cfg);
371 CHKPR(loadingImg, RET_ERR);
372 PushImg(cfg, loadingImg);
373 }
374 canvas.Rotate(cfg.rotationAngle, cfg.rotationFocusX, cfg.rotationFocusY);
375 canvas.DrawImage(*loadingImg, cfg.GetOffsetX(), cfg.GetOffsetY(), Rosen::Drawing::SamplingOptions());
376 } else {
377 RenderConfig runingLCfg = cfg;
378 runingLCfg.style_ = MOUSE_ICON::RUNNING_LEFT;
379 runingLCfg.align_ = ANGLE_NW;
380 runingLCfg.path_ = IMAGE_POINTER_DEFAULT_PATH + "Loading_Left.svg";
381 auto runningImgLeft = FindImg(runingLCfg);
382 if (runningImgLeft == nullptr) {
383 runningImgLeft = LoadPointerImage(runingLCfg);
384 CHKPR(runningImgLeft, RET_ERR);
385 PushImg(runingLCfg, runningImgLeft);
386 }
387 CHKPR(runningImgLeft, RET_ERR);
388 canvas.DrawImage(*runningImgLeft, runingLCfg.GetOffsetX(), runingLCfg.GetOffsetY(),
389 Rosen::Drawing::SamplingOptions());
390
391 RenderConfig runingRCfg = cfg;
392 runingRCfg.style_ = MOUSE_ICON::RUNNING_RIGHT;
393 runingRCfg.align_ = ANGLE_NW;
394 runingRCfg.path_ = IMAGE_POINTER_DEFAULT_PATH + "Loading_Right.svg";
395 auto runningImgRight = FindImg(runingRCfg);
396 if (runningImgRight == nullptr) {
397 runningImgRight = LoadPointerImage(runingRCfg);
398 CHKPR(runningImgRight, RET_ERR);
399 PushImg(runingRCfg, runningImgRight);
400 }
401 canvas.Rotate(runingRCfg.rotationAngle, runingRCfg.rotationFocusX, runingRCfg.rotationFocusY);
402 CHKPR(runningImgRight, RET_ERR);
403 canvas.DrawImage(*runningImgRight, runingRCfg.GetOffsetX(), runingRCfg.GetOffsetY(),
404 Rosen::Drawing::SamplingOptions());
405 }
406 return RET_OK;
407 }
408
DynamicRender(uint8_t * addr,uint32_t width,uint32_t height,const RenderConfig & cfg)409 int32_t PointerRenderer::DynamicRender(uint8_t *addr, uint32_t width, uint32_t height, const RenderConfig &cfg)
410 {
411 CHKPR(addr, RET_ERR);
412 uint32_t addrSize = width * height * RENDER_STRIDE;
413 if (cfg.style_ == MOUSE_ICON::TRANSPARENT_ICON) {
414 memset_s(addr, addrSize, 0, addrSize);
415 return RET_OK;
416 }
417
418 OHOS::Rosen::Drawing::Bitmap bitmap;
419 OHOS::Rosen::Drawing::BitmapFormat format { OHOS::Rosen::Drawing::COLORTYPE_RGBA_8888,
420 OHOS::Rosen::Drawing::ALPHATYPE_OPAQUE };
421 if (!bitmap.Build(width, height, format)) {
422 MMI_HILOGE("Bitmap build failed");
423 }
424 OHOS::Rosen::Drawing::Canvas canvas;
425 canvas.Bind(bitmap);
426 canvas.Clear(OHOS::Rosen::Drawing::Color::COLOR_TRANSPARENT);
427
428 OHOS::Rosen::Drawing::Pen pen;
429 pen.SetAntiAlias(true);
430 pen.SetColor(OHOS::Rosen::Drawing::Color::COLOR_BLUE);
431 OHOS::Rosen::Drawing::scalar penWidth = 1;
432 pen.SetWidth(penWidth);
433 canvas.AttachPen(pen);
434
435 OHOS::Rosen::Drawing::Brush brush;
436 brush.SetColor(Rosen::Drawing::Color::COLOR_TRANSPARENT);
437 canvas.DrawBackground(brush);
438
439 if (cfg.direction) {
440 int32_t directionFlag = -1;
441 int32_t degree = static_cast<int32_t>(directionFlag * static_cast<int32_t>(cfg.direction) * ROTATION_ANGLE90);
442 canvas.Rotate(degree, FOCUS_POINT, FOCUS_POINT);
443 }
444
445 if (DrawImage(canvas, cfg) != RET_OK) {
446 return RET_ERR;
447 }
448 errno_t ret = memcpy_s(addr, addrSize, bitmap.GetPixels(), bitmap.ComputeByteSize());
449 if (ret != EOK) {
450 return RET_ERR;
451 }
452 return RET_OK;
453 }
454 } // namespace OHOS::MMI