blob: a632f5952b0223d1a2b3947a65c450aa066eda26 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/wm/core/cursor_util.h"
#include <algorithm>
#include <array>
#include <cfloat>
#include <memory>
#include <optional>
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "cc/paint/skottie_wrapper.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/cursor_size.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/display/display_transform.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/skbitmap_operations.h"
#include "ui/lottie/animation.h"
#include "ui/resources/grit/ui_lottie_resources.h"
#include "ui/resources/grit/ui_resources.h"
namespace wm {
namespace {
using ::ui::mojom::CursorType;
using AnimationCache =
base::flat_map<CursorType, std::unique_ptr<lottie::Animation>>;
// Get a cache for lottie animations.
AnimationCache& GetAnimationCache() {
static base::NoDestructor<AnimationCache> cache;
return *cache;
}
// Converts the SkBitmap to use a different alpha type. Returns true if bitmap
// was modified, otherwise returns false.
bool ConvertSkBitmapAlphaType(SkBitmap* bitmap, SkAlphaType alpha_type) {
if (bitmap->info().alphaType() == alpha_type) {
return false;
}
// Copy the bitmap into a temporary buffer. This will convert alpha type.
SkImageInfo image_info =
SkImageInfo::MakeN32(bitmap->width(), bitmap->height(), alpha_type);
size_t info_row_bytes = image_info.minRowBytes();
std::vector<char> buffer(image_info.computeByteSize(info_row_bytes));
bitmap->readPixels(image_info, &buffer[0], info_row_bytes, 0, 0);
// Read the temporary buffer back into the original bitmap.
bitmap->reset();
bitmap->allocPixels(image_info);
// this memcpy call assumes bitmap->rowBytes() == info_row_bytes
UNSAFE_TODO(memcpy(bitmap->getPixels(), &buffer[0], buffer.size()));
return true;
}
// Only rotate the cursor's hotpoint. |hotpoint_in_out| is used as
// both input and output. |cursor_bitmap_width| and |cursor_bitmap_height|
// should be the width and height of the cursor before bitmap rotation.
void RotateCursorHotpoint(display::Display::Rotation rotation,
int cursor_bitmap_width,
int cursor_bitmap_height,
gfx::Point* hotpoint_in_out) {
switch (rotation) {
case display::Display::ROTATE_0:
break;
case display::Display::ROTATE_90:
hotpoint_in_out->SetPoint(cursor_bitmap_height - hotpoint_in_out->y(),
hotpoint_in_out->x());
break;
case display::Display::ROTATE_180:
hotpoint_in_out->SetPoint(cursor_bitmap_width - hotpoint_in_out->x(),
cursor_bitmap_height - hotpoint_in_out->y());
break;
case display::Display::ROTATE_270:
hotpoint_in_out->SetPoint(hotpoint_in_out->y(),
cursor_bitmap_width - hotpoint_in_out->x());
break;
}
}
// Rotate the cursor's bitmap and hotpoint.
// |bitmap_in_out| and |hotpoint_in_out| are used as
// both input and output.
void RotateCursorBitmapAndHotpoint(display::Display::Rotation rotation,
SkBitmap* bitmap_in_out,
gfx::Point* hotpoint_in_out) {
if (hotpoint_in_out) {
RotateCursorHotpoint(rotation, bitmap_in_out->width(),
bitmap_in_out->height(), hotpoint_in_out);
}
// SkBitmapOperations::Rotate() needs the bitmap to have premultiplied alpha.
DCHECK(rotation == display::Display::ROTATE_0 ||
bitmap_in_out->info().alphaType() != kUnpremul_SkAlphaType);
switch (rotation) {
case display::Display::ROTATE_0:
break;
case display::Display::ROTATE_90:
*bitmap_in_out = SkBitmapOperations::Rotate(
*bitmap_in_out, SkBitmapOperations::ROTATION_90_CW);
break;
case display::Display::ROTATE_180:
*bitmap_in_out = SkBitmapOperations::Rotate(
*bitmap_in_out, SkBitmapOperations::ROTATION_180_CW);
break;
case display::Display::ROTATE_270:
*bitmap_in_out = SkBitmapOperations::Rotate(
*bitmap_in_out, SkBitmapOperations::ROTATION_270_CW);
break;
}
}
// Create bitmaps from bitmap pixels. |image_rep| has a scale and holds
// bitmap pixels for that scale.
void CreateBitmapsFromPixels(const gfx::ImageSkiaRep& image_rep,
float scale,
display::Display::Rotation rotation,
bool is_animated,
std::vector<SkBitmap>* bitmaps_out,
gfx::Point* hotspot_in_out) {
CHECK(bitmaps_out->empty());
SkBitmap bitmap = image_rep.GetBitmap();
if (!is_animated) {
// Non-animated cursor.
ScaleAndRotateCursorBitmapAndHotpoint(scale / image_rep.scale(), rotation,
&bitmap, hotspot_in_out);
bitmaps_out->push_back(std::move(bitmap));
} else {
// Animated cursor.
// The image is assumed to be a concatenation of animation frames from
// left to right. Also, each frame is assumed to be square (width ==
// height).
const int frame_width = bitmap.height();
const int frame_height = frame_width;
const int total_width = bitmap.width();
CHECK_EQ(total_width % frame_width, 0);
const int frame_count = total_width / frame_width;
CHECK_GT(frame_count, 0);
for (int frame = 0; frame < frame_count; ++frame) {
const int x_offset = frame_width * frame;
SkBitmap cropped = SkBitmapOperations::CreateTiledBitmap(
bitmap, x_offset, 0, frame_width, frame_height);
ScaleAndRotateCursorBitmapAndHotpoint(
scale / image_rep.scale(), rotation, &cropped,
frame == 0 ? hotspot_in_out : nullptr);
bitmaps_out->push_back(std::move(cropped));
}
}
}
// Create bitmaps from static lottie. |image_rep| is unscaled and contains
// a paint record for the lottie animation.
void CreateBitmapsFromStaticLottie(const gfx::ImageSkiaRep& image_rep,
const gfx::Size& scaled_size,
float scale,
const gfx::Transform& rotation_transform,
std::vector<SkBitmap>* bitmaps_out) {
CHECK(bitmaps_out->empty());
// Non-animated cursor.
SkBitmap bitmap;
bitmap.allocN32Pixels(scaled_size.width(), scaled_size.height());
bitmap.eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(bitmap);
canvas.concat(TransformToSkM44(rotation_transform));
canvas.scale(scale, scale);
image_rep.GetPaintRecord().Playback(&canvas);
bitmap.setImmutable();
bitmaps_out->push_back(std::move(bitmap));
}
// Create bitmaps from animated lottie.
void CreateBitmapsFromAnimatedLottie(int resource_id,
const gfx::Size& scaled_size,
float scale,
const gfx::Transform& rotation_transform,
CursorType type,
std::vector<SkBitmap>* bitmaps_out) {
CHECK(bitmaps_out->empty());
AnimationCache& cursor_animations = GetAnimationCache();
if (!base::Contains(cursor_animations, type)) {
std::optional<std::vector<uint8_t>> lottie_bytes =
ui::ResourceBundle::GetSharedInstance().GetLottieData(resource_id);
scoped_refptr<cc::SkottieWrapper> skottie =
cc::SkottieWrapper::UnsafeCreateSerializable(std::move(*lottie_bytes));
cursor_animations[type] = std::make_unique<lottie::Animation>(skottie);
}
lottie::Animation* animation = cursor_animations[type].get();
const float cursor_animation_duration_in_second =
animation->GetAnimationDuration().InSecondsF();
// Target frame rate for animated cursor.
const int kAnimatedCursorFramePerSecond = 60;
const int frames =
kAnimatedCursorFramePerSecond * cursor_animation_duration_in_second;
for (int i = 0; i < frames; i++) {
float t = static_cast<float>(i) / frames;
SkBitmap bitmap;
bitmap.allocN32Pixels(scaled_size.width(), scaled_size.height());
bitmap.eraseColor(SK_ColorTRANSPARENT);
cc::SkiaPaintCanvas paint_canvas(bitmap);
gfx::Canvas canvas(&paint_canvas, scale);
canvas.Transform(rotation_transform);
animation->PaintFrame(&canvas, t, scaled_size);
bitmap.setImmutable();
bitmaps_out->push_back(std::move(bitmap));
}
}
struct CursorResourceData {
CursorType type;
int id;
gfx::Point hotspot_1x;
// TODO(crbug.com/416095366): Remove hotspot_2x when
// all cursor assets become lottie format.
gfx::Point hotspot_2x;
bool is_animated = false;
};
// Cursor resource data indexed by CursorType. Make sure to respect the order
// defined at ui/base/cursor/mojom/cursor_type.mojom.
constexpr auto kNormalCursorResourceData = std::to_array<
std::optional<CursorResourceData>>({
{{CursorType::kPointer, IDR_AURA_CURSOR_PTR_LOTTIE, {6, 4}, {6, 4}}},
{{CursorType::kCross, IDR_AURA_CURSOR_CROSSHAIR, {12, 12}, {24, 24}}},
{{CursorType::kHand, IDR_AURA_CURSOR_HAND, {9, 4}, {19, 8}}},
{{CursorType::kIBeam, IDR_AURA_CURSOR_IBEAM_LOTTIE, {12, 12}, {12, 12}}},
{{CursorType::kWait,
IDR_AURA_CURSOR_THROBBER_LOTTIE,
{12, 12},
{12, 12},
/*is_animated=*/true}},
{{CursorType::kHelp, IDR_AURA_CURSOR_HELP, {4, 4}, {8, 9}}},
{{CursorType::kEastResize,
IDR_AURA_CURSOR_EAST_RESIZE,
{12, 11},
{25, 23}}},
{{CursorType::kNorthResize,
IDR_AURA_CURSOR_NORTH_RESIZE,
{11, 12},
{23, 23}}},
{{CursorType::kNorthEastResize,
IDR_AURA_CURSOR_NORTH_EAST_RESIZE,
{12, 11},
{25, 23}}},
{{CursorType::kNorthWestResize,
IDR_AURA_CURSOR_NORTH_WEST_RESIZE,
{11, 11},
{24, 23}}},
{{CursorType::kSouthResize,
IDR_AURA_CURSOR_SOUTH_RESIZE,
{11, 12},
{23, 23}}},
{{CursorType::kSouthEastResize,
IDR_AURA_CURSOR_SOUTH_EAST_RESIZE,
{11, 11},
{24, 23}}},
{{CursorType::kSouthWestResize,
IDR_AURA_CURSOR_SOUTH_WEST_RESIZE,
{12, 11},
{25, 23}}},
{{CursorType::kWestResize,
IDR_AURA_CURSOR_WEST_RESIZE,
{12, 11},
{25, 23}}},
{{CursorType::kNorthSouthResize,
IDR_AURA_CURSOR_NORTH_SOUTH_RESIZE,
{11, 12},
{23, 23}}},
{{CursorType::kEastWestResize,
IDR_AURA_CURSOR_EAST_WEST_RESIZE,
{12, 11},
{25, 23}}},
{{CursorType::kNorthEastSouthWestResize,
IDR_AURA_CURSOR_NORTH_EAST_SOUTH_WEST_RESIZE,
{12, 11},
{25, 23}}},
{{CursorType::kNorthWestSouthEastResize,
IDR_AURA_CURSOR_NORTH_WEST_SOUTH_EAST_RESIZE,
{11, 11},
{24, 23}}},
{{CursorType::kColumnResize,
IDR_AURA_CURSOR_COL_RESIZE,
{12, 11},
{25, 23}}},
{{CursorType::kRowResize, IDR_AURA_CURSOR_ROW_RESIZE, {11, 12}, {23, 23}}},
/*CursorType::kMiddlePanning*/ {},
/*CursorType::kEastPanning*/ {},
/*CursorType::kNorthPanning*/ {},
/*CursorType::kNorthEastPanning*/ {},
/*CursorType::kNorthWestPanning*/ {},
/*CursorType::kSouthPanning*/ {},
/*CursorType::kSouthEastPanning*/ {},
/*CursorType::kSouthWestPanning*/ {},
/*CursorType::kWestPanning*/ {},
{{CursorType::kMove, IDR_AURA_CURSOR_MOVE, {11, 11}, {23, 23}}},
{{CursorType::kVerticalText,
IDR_AURA_CURSOR_XTERM_HORIZ,
{12, 11},
{26, 23}}},
{{CursorType::kCell, IDR_AURA_CURSOR_CELL, {11, 11}, {24, 23}}},
{{CursorType::kContextMenu, IDR_AURA_CURSOR_CONTEXT_MENU, {4, 4}, {8, 9}}},
{{CursorType::kAlias, IDR_AURA_CURSOR_ALIAS, {8, 6}, {15, 11}}},
{{CursorType::kProgress,
IDR_AURA_CURSOR_THROBBER_LOTTIE,
{12, 12},
{12, 12},
/*is_animated=*/true}},
{{CursorType::kNoDrop, IDR_AURA_CURSOR_NO_DROP, {9, 9}, {18, 18}}},
{{CursorType::kCopy, IDR_AURA_CURSOR_COPY, {9, 9}, {18, 18}}},
/*CursorType::kNone*/ {},
{{CursorType::kNotAllowed, IDR_AURA_CURSOR_NO_DROP, {9, 9}, {18, 18}}},
{{CursorType::kZoomIn, IDR_AURA_CURSOR_ZOOM_IN, {10, 10}, {20, 20}}},
{{CursorType::kZoomOut, IDR_AURA_CURSOR_ZOOM_OUT, {10, 10}, {20, 20}}},
{{CursorType::kGrab, IDR_AURA_CURSOR_GRAB, {8, 5}, {16, 10}}},
{{CursorType::kGrabbing, IDR_AURA_CURSOR_GRABBING, {9, 9}, {18, 18}}},
/*CursorType::kMiddlePanningVertical*/ {},
/*CursorType::kMiddlePanningHorizontal*/ {},
/*CursorType::kCustom*/ {},
/*CursorType::kDndNone*/ {},
/*CursorType::kDndMove*/ {},
/*CursorType::kDndCopy*/ {},
/*CursorType::kDndLink*/ {},
{{CursorType::kEastWestNoResize,
IDR_AURA_CURSOR_EAST_WEST_NO_RESIZE,
{12, 11},
{25, 23}}},
{{CursorType::kNorthSouthNoResize,
IDR_AURA_CURSOR_NORTH_SOUTH_NO_RESIZE,
{11, 12},
{23, 23}}},
{{CursorType::kNorthEastSouthWestNoResize,
IDR_AURA_CURSOR_NORTH_EAST_SOUTH_WEST_NO_RESIZE,
{12, 11},
{25, 23}}},
{{CursorType::kNorthWestSouthEastNoResize,
IDR_AURA_CURSOR_NORTH_WEST_SOUTH_EAST_NO_RESIZE,
{11, 11},
{24, 23}}},
});
static_assert(std::size(kNormalCursorResourceData) ==
static_cast<int>(CursorType::kMaxValue) + 1);
// TODO(crbug.com/416095366): Remove kLargeCursorResourceData when
// all cursor assets become lottie format.
constexpr auto kLargeCursorResourceData = std::to_array<
std::optional<CursorResourceData>>({
{{CursorType::kPointer, IDR_AURA_CURSOR_PTR_LOTTIE, {6, 4}, {6, 4}}},
{{CursorType::kCross, IDR_AURA_CURSOR_BIG_CROSSHAIR, {30, 32}, {60, 64}}},
{{CursorType::kHand, IDR_AURA_CURSOR_BIG_HAND, {25, 7}, {50, 14}}},
{{CursorType::kIBeam, IDR_AURA_CURSOR_IBEAM_LOTTIE, {12, 12}, {12, 12}}},
{{CursorType::kWait,
IDR_AURA_CURSOR_THROBBER_LOTTIE,
{12, 12},
{12, 12},
/*is_animated=*/true}},
{{CursorType::kHelp, IDR_AURA_CURSOR_BIG_HELP, {10, 11}, {20, 22}}},
{{CursorType::kEastResize,
IDR_AURA_CURSOR_BIG_EAST_RESIZE,
{35, 29},
{70, 58}}},
{{CursorType::kNorthResize,
IDR_AURA_CURSOR_BIG_NORTH_RESIZE,
{29, 32},
{58, 64}}},
{{CursorType::kNorthEastResize,
IDR_AURA_CURSOR_BIG_NORTH_EAST_RESIZE,
{31, 28},
{62, 56}}},
{{CursorType::kNorthWestResize,
IDR_AURA_CURSOR_BIG_NORTH_WEST_RESIZE,
{28, 28},
{56, 56}}},
{{CursorType::kSouthResize,
IDR_AURA_CURSOR_BIG_SOUTH_RESIZE,
{29, 32},
{58, 64}}},
{{CursorType::kSouthEastResize,
IDR_AURA_CURSOR_BIG_SOUTH_EAST_RESIZE,
{28, 28},
{56, 56}}},
{{CursorType::kSouthWestResize,
IDR_AURA_CURSOR_BIG_SOUTH_WEST_RESIZE,
{31, 28},
{62, 56}}},
{{CursorType::kWestResize,
IDR_AURA_CURSOR_BIG_WEST_RESIZE,
{35, 29},
{70, 58}}},
{{CursorType::kNorthSouthResize,
IDR_AURA_CURSOR_BIG_NORTH_SOUTH_RESIZE,
{29, 32},
{58, 64}}},
{{CursorType::kEastWestResize,
IDR_AURA_CURSOR_BIG_EAST_WEST_RESIZE,
{35, 29},
{70, 58}}},
{{CursorType::kNorthEastSouthWestResize,
IDR_AURA_CURSOR_BIG_NORTH_EAST_SOUTH_WEST_RESIZE,
{32, 30},
{64, 60}}},
{{CursorType::kNorthWestSouthEastResize,
IDR_AURA_CURSOR_BIG_NORTH_WEST_SOUTH_EAST_RESIZE,
{32, 31},
{64, 62}}},
{{CursorType::kColumnResize,
IDR_AURA_CURSOR_BIG_COL_RESIZE,
{35, 29},
{70, 58}}},
{{CursorType::kRowResize,
IDR_AURA_CURSOR_BIG_ROW_RESIZE,
{29, 32},
{58, 64}}},
/*CursorType::kMiddlePanning*/ {},
/*CursorType::kEastPanning*/ {},
/*CursorType::kNorthPanning*/ {},
/*CursorType::kNorthEastPanning*/ {},
/*CursorType::kNorthWestPanning*/ {},
/*CursorType::kSouthPanning*/ {},
/*CursorType::kSouthEastPanning*/ {},
/*CursorType::kSouthWestPanning*/ {},
/*CursorType::kWestPanning*/ {},
{{CursorType::kMove, IDR_AURA_CURSOR_BIG_MOVE, {32, 31}, {64, 62}}},
{{CursorType::kVerticalText,
IDR_AURA_CURSOR_BIG_XTERM_HORIZ,
{32, 30},
{64, 60}}},
{{CursorType::kCell, IDR_AURA_CURSOR_BIG_CELL, {30, 30}, {60, 60}}},
{{CursorType::kContextMenu,
IDR_AURA_CURSOR_BIG_CONTEXT_MENU,
{11, 11},
{22, 22}}},
{{CursorType::kAlias, IDR_AURA_CURSOR_BIG_ALIAS, {19, 11}, {38, 22}}},
{{CursorType::kProgress,
IDR_AURA_CURSOR_THROBBER_LOTTIE,
{12, 12},
{12, 12},
/*is_animated=*/true}},
{{CursorType::kNoDrop, IDR_AURA_CURSOR_BIG_NO_DROP, {10, 10}, {20, 20}}},
{{CursorType::kCopy, IDR_AURA_CURSOR_BIG_COPY, {21, 11}, {42, 22}}},
/*CursorType::kNone*/ {},
{{CursorType::kNotAllowed,
IDR_AURA_CURSOR_BIG_NO_DROP,
{10, 10},
{20, 20}}},
{{CursorType::kZoomIn, IDR_AURA_CURSOR_BIG_ZOOM_IN, {25, 26}, {50, 52}}},
{{CursorType::kZoomOut, IDR_AURA_CURSOR_BIG_ZOOM_OUT, {26, 26}, {52, 52}}},
{{CursorType::kGrab, IDR_AURA_CURSOR_BIG_GRAB, {21, 11}, {42, 22}}},
{{CursorType::kGrabbing, IDR_AURA_CURSOR_BIG_GRABBING, {20, 12}, {40, 24}}},
/*CursorType::kMiddlePanningVertical*/ {},
/*CursorType::kMiddlePanningHorizontal*/ {},
/*CursorType::kCustom*/ {},
/*CursorType::kDndNone*/ {},
/*CursorType::kDndMove*/ {},
/*CursorType::kDndCopy*/ {},
/*CursorType::kDndLink*/ {},
{{CursorType::kEastWestNoResize,
IDR_AURA_CURSOR_BIG_EAST_WEST_NO_RESIZE,
{35, 29},
{70, 58}}},
{{CursorType::kNorthSouthNoResize,
IDR_AURA_CURSOR_BIG_NORTH_SOUTH_NO_RESIZE,
{29, 32},
{58, 64}}},
{{CursorType::kNorthEastSouthWestNoResize,
IDR_AURA_CURSOR_BIG_NORTH_EAST_SOUTH_WEST_NO_RESIZE,
{32, 30},
{64, 60}}},
{{CursorType::kNorthWestSouthEastNoResize,
IDR_AURA_CURSOR_BIG_NORTH_WEST_SOUTH_EAST_NO_RESIZE,
{32, 31},
{64, 62}}},
});
static_assert(std::size(kLargeCursorResourceData) ==
static_cast<int>(CursorType::kMaxValue) + 1);
} // namespace
std::optional<ui::CursorData> GetCursorData(
CursorType type,
ui::CursorSize size,
float scale,
std::optional<int> target_cursor_size_in_px,
display::Display::Rotation rotation,
SkColor color) {
DCHECK_NE(type, CursorType::kCustom);
int resource_id;
gfx::Point hotspot;
bool is_animated;
if (!GetCursorDataFor(size, type, scale, &resource_id, &hotspot,
&is_animated)) {
return std::nullopt;
}
DCHECK_NE(type, CursorType::kNone);
std::vector<SkBitmap> bitmaps;
const gfx::ImageSkia* image =
ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id);
const float resource_scale = ui::GetScaleForResourceScaleFactor(
ui::GetSupportedResourceScaleFactorForRescale(scale));
const gfx::ImageSkiaRep& image_rep = image->GetRepresentation(resource_scale);
CHECK(image_rep.unscaled() || (image_rep.scale() == resource_scale));
if (target_cursor_size_in_px) {
// If `target_cursor_size_in_px` presents, use it to calculate scale.
// Use `image_rep.GetHeight()` as cursor dp size. An animated bitmap
// is composed of horizontally tiled frames so its width could not
// be used as cursor size.
int cursor_size_in_dp = image_rep.GetHeight();
scale = static_cast<float>(target_cursor_size_in_px.value()) /
static_cast<float>(cursor_size_in_dp);
}
if (!image_rep.unscaled()) {
// Bitmap-based cursor image.
CreateBitmapsFromPixels(image_rep, scale, rotation, is_animated, &bitmaps,
&hotspot);
} else {
const gfx::Size scaled_size = ScaleToRoundedSize(
gfx::Size(image_rep.GetWidth(), image_rep.GetHeight()), scale);
const gfx::Transform rotation_transform = display::CreateRotationTransform(
rotation, gfx::SizeF(scaled_size.width(), scaled_size.height()));
hotspot = gfx::ScaleToFlooredPoint(hotspot, scale);
RotateCursorHotpoint(rotation, scaled_size.width(), scaled_size.height(),
&hotspot);
if (!is_animated) {
// Non-animated lottie cursor.
CreateBitmapsFromStaticLottie(image_rep, scaled_size, scale,
rotation_transform, &bitmaps);
} else {
// Animated lottie cursor.
CreateBitmapsFromAnimatedLottie(resource_id, scaled_size, scale,
rotation_transform, type, &bitmaps);
}
}
if (color != ui::kDefaultCursorColor) {
std::for_each(bitmaps.begin(), bitmaps.end(), [&](SkBitmap& bitmap) {
bitmap = GetColorAdjustedBitmap(bitmap, color);
});
}
return ui::CursorData(std::move(bitmaps), std::move(hotspot), scale);
}
void ScaleAndRotateCursorBitmapAndHotpoint(float scale,
display::Display::Rotation rotation,
SkBitmap* bitmap,
gfx::Point* hotpoint) {
if (scale < FLT_EPSILON) {
NOTREACHED() << "Scale must be larger than 0.";
}
// SkBitmapOperations::Rotate() and skia::ImageOperations::Resize()
// need the bitmap to have premultiplied alpha, so convert bitmap alpha type
// if we are going to rotate or scale.
bool was_converted = false;
if ((rotation != display::Display::ROTATE_0 || scale != 1.0f) &&
bitmap->info().alphaType() == kUnpremul_SkAlphaType) {
ConvertSkBitmapAlphaType(bitmap, kPremul_SkAlphaType);
was_converted = true;
}
RotateCursorBitmapAndHotpoint(rotation, bitmap, hotpoint);
if (scale == 1.0f) {
if (was_converted) {
ConvertSkBitmapAlphaType(bitmap, kUnpremul_SkAlphaType);
}
return;
}
gfx::Size scaled_size = gfx::ScaleToFlooredSize(
gfx::Size(bitmap->width(), bitmap->height()), scale);
SkBitmap scaled_bitmap;
// Use RESIZE_BEST to avoid blurry large cursor on external displays.
// See crbug.com/1229231.
scaled_bitmap =
skia::ImageOperations::Resize(*bitmap, skia::ImageOperations::RESIZE_BEST,
scaled_size.width(), scaled_size.height());
if (was_converted) {
ConvertSkBitmapAlphaType(&scaled_bitmap, kUnpremul_SkAlphaType);
}
*bitmap = scaled_bitmap;
if (hotpoint) {
*hotpoint = gfx::ScaleToFlooredPoint(*hotpoint, scale);
}
}
bool GetCursorDataFor(ui::CursorSize cursor_size,
CursorType type,
float scale_factor,
int* resource_id,
gfx::Point* point,
bool* is_animated) {
DCHECK_NE(type, CursorType::kCustom);
// TODO(https://crbug.com/1270302: temporary check until GetCursorDataFor is
// replaced by GetCursorData, which is only used internally by CursorLoader.
if (type == CursorType::kNone) {
return false;
}
// TODO(htts://crbug.com/1190818): currently, kNull is treated as kPointer.
CursorType t = type == CursorType::kNull ? CursorType::kPointer : type;
std::optional<CursorResourceData> resource =
cursor_size == ui::CursorSize::kNormal
? kNormalCursorResourceData[static_cast<int>(t)]
: kLargeCursorResourceData[static_cast<int>(t)];
if (!resource) {
return false;
}
DCHECK_EQ(resource->type, t);
*resource_id = resource->id;
*point = resource->hotspot_1x;
if (ui::GetSupportedResourceScaleFactorForRescale(scale_factor) ==
ui::k200Percent) {
*point = resource->hotspot_2x;
}
*is_animated = resource->is_animated;
return true;
}
SkBitmap GetColorAdjustedBitmap(const SkBitmap& bitmap, SkColor cursor_color) {
// Recolor the black and greyscale parts of the image based on
// `cursor_color`. Do not recolor pure white or tinted portions of the image,
// this ensures we do not impact the colored portions of cursors or the
// transition between the colored portion and white outline.
// TODO(b:376929449): Programmatically find a way to recolor the white
// parts in order to draw a black outline, but without impacting cursors
// like noDrop which contained tinted portions. Or, add new assets with
// black and white inverted for easier re-coloring.
SkBitmap recolored;
recolored.allocN32Pixels(bitmap.width(), bitmap.height());
recolored.eraseARGB(0, 0, 0, 0);
SkCanvas canvas(recolored);
canvas.drawImage(bitmap.asImage(), 0, 0);
color_utils::HSL cursor_hsl;
color_utils::SkColorToHSL(cursor_color, &cursor_hsl);
for (int y = 0; y < bitmap.height(); ++y) {
for (int x = 0; x < bitmap.width(); ++x) {
const SkColor color = bitmap.getColor(x, y);
// If the alpha is lower than 1, it's transparent, skip it.
if (SkColorGetA(color) < 1) {
continue;
}
// Convert to HSL: We want to change the hue and saturation, and
// map the lightness from 0-100 to cursor_hsl.l-100. This means that
// things which were black (l=0) become the cursor color lightness, and
// things which were white (l=100) stay white.
color_utils::HSL hsl;
color_utils::SkColorToHSL(color, &hsl);
// If it has color, do not change it.
if (hsl.s > 0.01) {
continue;
}
color_utils::HSL result;
result.h = cursor_hsl.h;
result.s = cursor_hsl.s;
result.l = hsl.l * (1 - cursor_hsl.l) + cursor_hsl.l;
SkPaint paint;
paint.setColor(color_utils::HSLToSkColor(result, SkColorGetA(color)));
canvas.drawRect(SkRect::MakeXYWH(x, y, 1, 1), paint);
}
}
return recolored;
}
} // namespace wm