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 const std::string IMAGE_POINTER_DEFAULT_PATH = "/system/etc/multimodalinput/mouse_icon/";
39
40 namespace OHOS::MMI {
41
GetImageSize() const42 int32_t RenderConfig::GetImageSize() const
43 {
44 return pow(INCREASE_RATIO, size - 1) * dpi * DEVCIE_INDEPENDENT_PIXELS;
45 }
46
ToString() const47 std::string RenderConfig::ToString() const
48 {
49 std::ostringstream oss;
50 oss << "{style=" << style << ", align=" << align << ", path" << path << ", color=" << color
51 << ", size=" << size << ", rotationAngle=" << rotationAngle
52 << ", [" << rotationFocusX << " " <<rotationFocusY << "]"
53 <<", dpi=" << dpi
54 << ", isHard=" << isHard << ", ImageSize=" << GetImageSize() << "}";
55 return oss.str();
56 }
57
GetOffsetX() const58 int32_t RenderConfig::GetOffsetX() const
59 {
60 int32_t width = this->GetImageSize();
61 switch (this->align) {
62 case ANGLE_E:
63 return FOCUS_POINT;
64 case ANGLE_S:
65 return FOCUS_POINT - width / CALCULATE_IMAGE_MIDDLE;
66 case ANGLE_W:
67 return FOCUS_POINT - width;
68 case ANGLE_N:
69 return FOCUS_POINT - width / CALCULATE_IMAGE_MIDDLE;
70 case ANGLE_SE:
71 return FOCUS_POINT - width;
72 case ANGLE_NE:
73 return FOCUS_POINT - width;
74 case ANGLE_SW:
75 return FOCUS_POINT;
76 case ANGLE_NW:
77 return FOCUS_POINT - this->userIconHotSpotX;
78 case ANGLE_CENTER:
79 return FOCUS_POINT - width / CALCULATE_IMAGE_MIDDLE;
80 case ANGLE_NW_RIGHT:
81 return FOCUS_POINT - width * CALCULATE_MOUSE_ICON_BIAS;
82 default:
83 MMI_HILOGW("No need calculate physicalX offset");
84 return FOCUS_POINT;
85 }
86 }
87
GetOffsetY() const88 int32_t RenderConfig::GetOffsetY() const
89 {
90 int32_t height = this->GetImageSize();
91 switch (this->align) {
92 case ANGLE_E:
93 return FOCUS_POINT - height / CALCULATE_IMAGE_MIDDLE;
94 case ANGLE_S:
95 return FOCUS_POINT;
96 case ANGLE_W:
97 return FOCUS_POINT - height;
98 case ANGLE_N:
99 return FOCUS_POINT - height;
100 case ANGLE_SE:
101 return FOCUS_POINT - height;
102 case ANGLE_NE:
103 return FOCUS_POINT;
104 case ANGLE_SW:
105 return FOCUS_POINT - height;
106 case ANGLE_NW:
107 return FOCUS_POINT - this->userIconHotSpotY;
108 case ANGLE_CENTER:
109 return FOCUS_POINT - height / CALCULATE_IMAGE_MIDDLE;
110 case ANGLE_NW_RIGHT:
111 return FOCUS_POINT;
112 default:
113 MMI_HILOGW("No need calculate physicalY offset");
114 return FOCUS_POINT;
115 }
116 }
117
UserIconScale(uint32_t width,uint32_t height,const RenderConfig & cfg)118 image_ptr_t PointerRenderer::UserIconScale(uint32_t width, uint32_t height, const RenderConfig &cfg)
119 {
120 image_ptr_t image = nullptr;
121 image = ExtractDrawingImage(cfg.userIconPixelMap);
122 return image;
123 }
124
Render(uint8_t * addr,uint32_t width,uint32_t height,const RenderConfig & cfg)125 int32_t PointerRenderer::Render(uint8_t *addr, uint32_t width, uint32_t height, const RenderConfig &cfg)
126 {
127 CHKPR(addr, RET_ERR);
128 MMI_HILOGI("Render %{public}s", cfg.ToString().c_str());
129
130 uint32_t addrSize = width * height * RENDER_STRIDE;
131 if (cfg.style == MOUSE_ICON::TRANSPARENT_ICON) {
132 memset_s(addr, addrSize, 0, addrSize);
133 return RET_OK;
134 }
135
136 // construct bitmap
137 OHOS::Rosen::Drawing::Bitmap bitmap;
138 OHOS::Rosen::Drawing::BitmapFormat format {
139 OHOS::Rosen::Drawing::COLORTYPE_RGBA_8888,
140 OHOS::Rosen::Drawing::ALPHATYPE_OPAQUE,
141 };
142 bitmap.Build(width, height, format);
143
144 // construct canvas and bind to bitmap
145 OHOS::Rosen::Drawing::Canvas canvas;
146 canvas.Bind(bitmap);
147 canvas.Clear(OHOS::Rosen::Drawing::Color::COLOR_TRANSPARENT);
148 if (cfg.direction) {
149 int32_t directionFlag = cfg.isHard ? -1 : 0;
150 int32_t degree = static_cast<int32_t>(directionFlag * static_cast<int32_t>(cfg.direction) * ROTATION_ANGLE90);
151 canvas.Rotate(degree, FOCUS_POINT, FOCUS_POINT);
152 }
153 image_ptr_t image = nullptr;
154 if (cfg.userIconPixelMap == nullptr) {
155 image = LoadPointerImage(cfg);
156 } else {
157 image = UserIconScale(width, height, cfg);
158 }
159 CHKPR(image, RET_ERR);
160 //Draw image on canvas
161 canvas.DrawImage(*image, cfg.GetOffsetX(), cfg.GetOffsetY(), Rosen::Drawing::SamplingOptions());
162
163 errno_t ret = memcpy_s(addr, addrSize, bitmap.GetPixels(), addrSize);
164 if (ret != EOK) {
165 return RET_ERR;
166 }
167 return RET_OK;
168 }
169
LoadPointerImage(const RenderConfig & cfg)170 image_ptr_t PointerRenderer::LoadPointerImage(const RenderConfig &cfg)
171 {
172 auto pixelmap = LoadCursorSvgWithColor(cfg);
173 return ExtractDrawingImage(pixelmap);
174 }
175
ChangeSvgCursorColor(std::string & str,int32_t color)176 static void ChangeSvgCursorColor(std::string& str, int32_t color)
177 {
178 std::string targetColor = IntToHexRGB(color);
179 StringReplace(str, "#000000", targetColor);
180 if (color == MAX_POINTER_COLOR) {
181 // stroke=\"#FFFFFF" fill="#000000" stroke-linejoin="round" transform="xxx"
182 std::regex re("(<path.*)(stroke=\"#[a-fA-F0-9]{6}\")(.*path>)");
183 str = std::regex_replace(str, re, "$1stroke=\"#000000\"$3");
184 }
185 }
186
SetCursorColorBaseOnStyle(const RenderConfig & cfg,OHOS::Media::DecodeOptions & decodeOpts)187 void SetCursorColorBaseOnStyle(const RenderConfig &cfg, OHOS::Media::DecodeOptions &decodeOpts)
188 {
189 const bool isHandColor =
190 (cfg.style == HAND_GRABBING) ||(cfg.style == HAND_OPEN) || (cfg.style == HAND_POINTING);
191 if (isHandColor) {
192 if (cfg.color == MAX_POINTER_COLOR ||
193 cfg.color == MIN_POINTER_COLOR ||
194 cfg.color == OTHER_POINTER_COLOR) {
195 decodeOpts.SVGOpts.fillColor = {.isValidColor = true, .color = MAX_POINTER_COLOR};
196 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MIN_POINTER_COLOR};
197 } else {
198 decodeOpts.SVGOpts.fillColor = {.isValidColor = true, .color = cfg.color};
199 if (cfg.color == MAX_POINTER_COLOR) {
200 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MIN_POINTER_COLOR};
201 } else {
202 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MAX_POINTER_COLOR};
203 }
204 }
205 }
206 }
207
LoadCursorSvgWithColor(const RenderConfig & cfg)208 pixelmap_ptr_t PointerRenderer::LoadCursorSvgWithColor(const RenderConfig &cfg)
209 {
210 std::string svgContent;
211 if (!ReadFile(cfg.path, svgContent)) {
212 MMI_HILOGE("read file failed");
213 return nullptr;
214 }
215
216 const bool isPartColor = (cfg.style == CURSOR_COPY) || (cfg.style == CURSOR_FORBID) || (cfg.style == HELP);
217 if (isPartColor) {
218 ChangeSvgCursorColor(svgContent, cfg.color);
219 }
220 OHOS::Media::SourceOptions opts;
221 std::unique_ptr<std::istream> isp(std::make_unique<std::istringstream>(svgContent));
222 uint32_t ret = 0;
223 auto imageSource = OHOS::Media::ImageSource::CreateImageSource(std::move(isp), opts, ret);
224 if (!imageSource || ret != ERR_OK) {
225 MMI_HILOGE("Get ImageSource failed, ret=%{public}d", ret);
226 }
227 CHKPP(imageSource);
228
229 int32_t imgSize = cfg.GetImageSize();
230 OHOS::Media::DecodeOptions decodeOpts;
231 decodeOpts.desiredSize = {.width = imgSize, .height = imgSize};
232 if (!isPartColor) {
233 decodeOpts.SVGOpts.fillColor = {.isValidColor = true, .color = cfg.color};
234 if (cfg.color == MAX_POINTER_COLOR) {
235 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MIN_POINTER_COLOR};
236 } else {
237 decodeOpts.SVGOpts.strokeColor = {.isValidColor = true, .color = MAX_POINTER_COLOR};
238 }
239 SetCursorColorBaseOnStyle(cfg, decodeOpts);
240 }
241
242 pixelmap_ptr_t pmap = imageSource->CreatePixelMap(decodeOpts, ret);
243 return pmap;
244 }
245
246 class PixelMapContext {
247 public:
PixelMapContext(pixelmap_ptr_t pixelMap)248 explicit PixelMapContext(pixelmap_ptr_t pixelMap) : pixelMap_(pixelMap) {}
~PixelMapContext()249 ~PixelMapContext()
250 {
251 pixelMap_ = nullptr;
252 }
253
254 private:
255 pixelmap_ptr_t pixelMap_{nullptr};
256 };
257
PixelMapReleaseProc(const void *,void * context)258 static void PixelMapReleaseProc(const void * /* pixels */, void *context)
259 {
260 PixelMapContext *ctx = static_cast<PixelMapContext *>(context);
261 if (ctx != nullptr) {
262 delete ctx;
263 }
264 }
265
266
PixelFormatToColorType(Media::PixelFormat pixelFormat)267 static Rosen::Drawing::ColorType PixelFormatToColorType(Media::PixelFormat pixelFormat)
268 {
269 switch (pixelFormat) {
270 case Media::PixelFormat::RGB_565:
271 return Rosen::Drawing::ColorType::COLORTYPE_RGB_565;
272 case Media::PixelFormat::RGBA_8888:
273 return Rosen::Drawing::ColorType::COLORTYPE_RGBA_8888;
274 case Media::PixelFormat::BGRA_8888:
275 return Rosen::Drawing::ColorType::COLORTYPE_BGRA_8888;
276 case Media::PixelFormat::ALPHA_8:
277 return Rosen::Drawing::ColorType::COLORTYPE_ALPHA_8;
278 case Media::PixelFormat::RGBA_F16:
279 return Rosen::Drawing::ColorType::COLORTYPE_RGBA_F16;
280 case Media::PixelFormat::UNKNOWN:
281 case Media::PixelFormat::ARGB_8888:
282 case Media::PixelFormat::RGB_888:
283 case Media::PixelFormat::NV21:
284 case Media::PixelFormat::NV12:
285 case Media::PixelFormat::CMYK:
286 default:
287 return Rosen::Drawing::ColorType::COLORTYPE_UNKNOWN;
288 }
289 }
290
AlphaTypeToAlphaType(Media::AlphaType alphaType)291 static Rosen::Drawing::AlphaType AlphaTypeToAlphaType(Media::AlphaType alphaType)
292 {
293 switch (alphaType) {
294 case Media::AlphaType::IMAGE_ALPHA_TYPE_UNKNOWN:
295 return Rosen::Drawing::AlphaType::ALPHATYPE_UNKNOWN;
296 case Media::AlphaType::IMAGE_ALPHA_TYPE_OPAQUE:
297 return Rosen::Drawing::AlphaType::ALPHATYPE_OPAQUE;
298 case Media::AlphaType::IMAGE_ALPHA_TYPE_PREMUL:
299 return Rosen::Drawing::AlphaType::ALPHATYPE_PREMUL;
300 case Media::AlphaType::IMAGE_ALPHA_TYPE_UNPREMUL:
301 return Rosen::Drawing::AlphaType::ALPHATYPE_UNPREMUL;
302 default:
303 return Rosen::Drawing::AlphaType::ALPHATYPE_UNKNOWN;
304 }
305 }
306
ConvertToColorSpace(Media::ColorSpace colorSpace)307 static std::shared_ptr<Rosen::Drawing::ColorSpace> ConvertToColorSpace(Media::ColorSpace colorSpace)
308 {
309 switch (colorSpace) {
310 case Media::ColorSpace::DISPLAY_P3:
311 return Rosen::Drawing::ColorSpace::CreateRGB(
312 Rosen::Drawing::CMSTransferFuncType::SRGB, Rosen::Drawing::CMSMatrixType::DCIP3);
313 case Media::ColorSpace::LINEAR_SRGB:
314 return Rosen::Drawing::ColorSpace::CreateSRGBLinear();
315 case Media::ColorSpace::SRGB:
316 return Rosen::Drawing::ColorSpace::CreateSRGB();
317 default:
318 return Rosen::Drawing::ColorSpace::CreateSRGB();
319 }
320 }
321
ExtractDrawingImage(pixelmap_ptr_t pixelMap)322 image_ptr_t PointerRenderer::ExtractDrawingImage(pixelmap_ptr_t pixelMap)
323 {
324 CHKPP(pixelMap);
325 Media::ImageInfo imageInfo;
326 pixelMap->GetImageInfo(imageInfo);
327 Rosen::Drawing::ImageInfo drawingImageInfo {
328 imageInfo.size.width,
329 imageInfo.size.height,
330 PixelFormatToColorType(imageInfo.pixelFormat),
331 AlphaTypeToAlphaType(imageInfo.alphaType),
332 ConvertToColorSpace(imageInfo.colorSpace),
333 };
334 Rosen::Drawing::Pixmap imagePixmap(drawingImageInfo, reinterpret_cast<const void*>(pixelMap->GetPixels()),
335 pixelMap->GetRowBytes());
336 PixelMapContext *releaseContext = new (std::nothrow) PixelMapContext(pixelMap);
337 CHKPP(releaseContext);
338 auto image = Rosen::Drawing::Image::MakeFromRaster(imagePixmap, PixelMapReleaseProc, releaseContext);
339 if (image == nullptr) {
340 MMI_HILOGE("ExtractDrawingImage image fail");
341 delete releaseContext;
342 }
343 return image;
344 }
345
DrawImage(OHOS::Rosen::Drawing::Canvas & canvas,const RenderConfig & cfg)346 int32_t PointerRenderer::DrawImage(OHOS::Rosen::Drawing::Canvas &canvas, const RenderConfig &cfg)
347 {
348 if (cfg.style == MOUSE_ICON::LOADING) {
349 auto loadingImg = FindImg(cfg);
350 if (loadingImg == nullptr) {
351 loadingImg = LoadPointerImage(cfg);
352 CHKPR(loadingImg, RET_ERR);
353 PushImg(cfg, loadingImg);
354 }
355 canvas.Rotate(cfg.rotationAngle, cfg.rotationFocusX, cfg.rotationFocusY);
356 canvas.DrawImage(*loadingImg, cfg.GetOffsetX(), cfg.GetOffsetY(), Rosen::Drawing::SamplingOptions());
357 } else {
358 RenderConfig runingLCfg = cfg;
359 runingLCfg.style = MOUSE_ICON::RUNNING_LEFT;
360 runingLCfg.align = ANGLE_NW;
361 runingLCfg.path = IMAGE_POINTER_DEFAULT_PATH + "Loading_Left.svg";
362 auto runningImgLeft = FindImg(runingLCfg);
363 if (runningImgLeft == nullptr) {
364 runningImgLeft = LoadPointerImage(runingLCfg);
365 CHKPR(runningImgLeft, RET_ERR);
366 PushImg(runingLCfg, runningImgLeft);
367 }
368 CHKPR(runningImgLeft, RET_ERR);
369 canvas.DrawImage(*runningImgLeft, runingLCfg.GetOffsetX(), runingLCfg.GetOffsetY(),
370 Rosen::Drawing::SamplingOptions());
371
372 RenderConfig runingRCfg = cfg;
373 runingRCfg.style = MOUSE_ICON::RUNNING_RIGHT;
374 runingRCfg.align = ANGLE_NW;
375 runingRCfg.path = IMAGE_POINTER_DEFAULT_PATH + "Loading_Right.svg";
376 auto runningImgRight = FindImg(runingRCfg);
377 if (runningImgRight == nullptr) {
378 runningImgRight = LoadPointerImage(runingRCfg);
379 CHKPR(runningImgRight, RET_ERR);
380 PushImg(runingRCfg, runningImgRight);
381 }
382 canvas.Rotate(runingRCfg.rotationAngle, runingRCfg.rotationFocusX, runingRCfg.rotationFocusY);
383 CHKPR(runningImgRight, RET_ERR);
384 canvas.DrawImage(*runningImgRight, runingRCfg.GetOffsetX(), runingRCfg.GetOffsetY(),
385 Rosen::Drawing::SamplingOptions());
386 }
387 return RET_OK;
388 }
389
DynamicRender(uint8_t * addr,uint32_t width,uint32_t height,const RenderConfig & cfg)390 int32_t PointerRenderer::DynamicRender(uint8_t *addr, uint32_t width, uint32_t height, const RenderConfig &cfg)
391 {
392 CHKPR(addr, RET_ERR);
393 uint32_t addrSize = width * height * RENDER_STRIDE;
394 if (cfg.style == MOUSE_ICON::TRANSPARENT_ICON) {
395 memset_s(addr, addrSize, 0, addrSize);
396 return RET_OK;
397 }
398
399 OHOS::Rosen::Drawing::Bitmap bitmap;
400 OHOS::Rosen::Drawing::BitmapFormat format { OHOS::Rosen::Drawing::COLORTYPE_RGBA_8888,
401 OHOS::Rosen::Drawing::ALPHATYPE_OPAQUE };
402 bitmap.Build(width, height, format);
403
404 OHOS::Rosen::Drawing::Canvas canvas;
405 canvas.Bind(bitmap);
406 canvas.Clear(OHOS::Rosen::Drawing::Color::COLOR_TRANSPARENT);
407
408 OHOS::Rosen::Drawing::Pen pen;
409 pen.SetAntiAlias(true);
410 pen.SetColor(OHOS::Rosen::Drawing::Color::COLOR_BLUE);
411 OHOS::Rosen::Drawing::scalar penWidth = 1;
412 pen.SetWidth(penWidth);
413 canvas.AttachPen(pen);
414
415 OHOS::Rosen::Drawing::Brush brush;
416 brush.SetColor(Rosen::Drawing::Color::COLOR_TRANSPARENT);
417 canvas.DrawBackground(brush);
418
419 if (cfg.direction) {
420 int32_t directionFlag = cfg.isHard ? -1 : 0;
421 int32_t degree = static_cast<int32_t>(directionFlag * static_cast<int32_t>(cfg.direction) * ROTATION_ANGLE90);
422 canvas.Rotate(degree, FOCUS_POINT, FOCUS_POINT);
423 }
424
425 if (DrawImage(canvas, cfg) != RET_OK) {
426 return RET_ERR;
427 }
428 errno_t ret = memcpy_s(addr, addrSize, bitmap.GetPixels(), addrSize);
429 if (ret != EOK) {
430 return RET_ERR;
431 }
432 return RET_OK;
433 }
434 } // namespace OHOS::MMI