cursor.cc (8430B)
1 /* 2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include "modules/desktop_capture/win/cursor.h" 12 13 #include <cstddef> 14 #include <cstdint> 15 #include <cstring> 16 #include <memory> 17 18 #include "modules/desktop_capture/desktop_frame.h" 19 #include "modules/desktop_capture/desktop_geometry.h" 20 #include "modules/desktop_capture/mouse_cursor.h" 21 #include "modules/desktop_capture/win/scoped_gdi_object.h" 22 #include "rtc_base/logging.h" 23 #include "rtc_base/system/arch.h" 24 25 namespace webrtc { 26 27 namespace { 28 29 #if defined(WEBRTC_ARCH_LITTLE_ENDIAN) 30 31 #define RGBA(r, g, b, a) \ 32 ((((a) << 24) & 0xff000000) | (((b) << 16) & 0xff0000) | \ 33 (((g) << 8) & 0xff00) | ((r) & 0xff)) 34 35 #else // !defined(WEBRTC_ARCH_LITTLE_ENDIAN) 36 37 #define RGBA(r, g, b, a) \ 38 ((((r) << 24) & 0xff000000) | (((g) << 16) & 0xff0000) | \ 39 (((b) << 8) & 0xff00) | ((a) & 0xff)) 40 41 #endif // !defined(WEBRTC_ARCH_LITTLE_ENDIAN) 42 43 const int kBytesPerPixel = DesktopFrame::kBytesPerPixel; 44 45 // Pixel colors used when generating cursor outlines. 46 const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff); 47 const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff); 48 const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0); 49 50 const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff); 51 52 // Expands the cursor shape to add a white outline for visibility against 53 // dark backgrounds. 54 void AddCursorOutline(int width, int height, uint32_t* data) { 55 for (int y = 0; y < height; y++) { 56 for (int x = 0; x < width; x++) { 57 // If this is a transparent pixel (bgr == 0 and alpha = 0), check the 58 // neighbor pixels to see if this should be changed to an outline pixel. 59 if (*data == kPixelRgbaTransparent) { 60 // Change to white pixel if any neighbors (top, bottom, left, right) 61 // are black. 62 if ((y > 0 && data[-width] == kPixelRgbaBlack) || 63 (y < height - 1 && data[width] == kPixelRgbaBlack) || 64 (x > 0 && data[-1] == kPixelRgbaBlack) || 65 (x < width - 1 && data[1] == kPixelRgbaBlack)) { 66 *data = kPixelRgbaWhite; 67 } 68 } 69 data++; 70 } 71 } 72 } 73 74 // Premultiplies RGB components of the pixel data in the given image by 75 // the corresponding alpha components. 76 void AlphaMul(uint32_t* data, int width, int height) { 77 static_assert(sizeof(uint32_t) == kBytesPerPixel, 78 "size of uint32 should be the number of bytes per pixel"); 79 80 for (uint32_t* data_end = data + width * height; data != data_end; ++data) { 81 RGBQUAD* from = reinterpret_cast<RGBQUAD*>(data); 82 RGBQUAD* to = reinterpret_cast<RGBQUAD*>(data); 83 to->rgbBlue = 84 (static_cast<uint16_t>(from->rgbBlue) * from->rgbReserved) / 0xff; 85 to->rgbGreen = 86 (static_cast<uint16_t>(from->rgbGreen) * from->rgbReserved) / 0xff; 87 to->rgbRed = 88 (static_cast<uint16_t>(from->rgbRed) * from->rgbReserved) / 0xff; 89 } 90 } 91 92 // Scans a 32bpp bitmap looking for any pixels with non-zero alpha component. 93 // Returns true if non-zero alpha is found. `stride` is expressed in pixels. 94 bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height) { 95 const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data); 96 for (int y = 0; y < height; ++y) { 97 for (int x = 0; x < width; ++x) { 98 if (plane->rgbReserved != 0) 99 return true; 100 plane += 1; 101 } 102 plane += stride - width; 103 } 104 105 return false; 106 } 107 108 } // namespace 109 110 MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) { 111 ICONINFO iinfo; 112 if (!GetIconInfo(cursor, &iinfo)) { 113 RTC_LOG_F(LS_ERROR) << "Unable to get cursor icon info. Error = " 114 << GetLastError(); 115 return NULL; 116 } 117 118 int hotspot_x = iinfo.xHotspot; 119 int hotspot_y = iinfo.yHotspot; 120 121 // Make sure the bitmaps will be freed. 122 win::ScopedBitmap scoped_mask(iinfo.hbmMask); 123 win::ScopedBitmap scoped_color(iinfo.hbmColor); 124 bool is_color = iinfo.hbmColor != NULL; 125 126 // Get `scoped_mask` dimensions. 127 BITMAP bitmap_info; 128 if (!GetObject(scoped_mask, sizeof(bitmap_info), &bitmap_info)) { 129 RTC_LOG_F(LS_ERROR) << "Unable to get bitmap info. Error = " 130 << GetLastError(); 131 return NULL; 132 } 133 134 int width = bitmap_info.bmWidth; 135 int height = bitmap_info.bmHeight; 136 std::unique_ptr<uint32_t[]> mask_data(new uint32_t[width * height]); 137 138 // Get pixel data from `scoped_mask` converting it to 32bpp along the way. 139 // GetDIBits() sets the alpha component of every pixel to 0. 140 BITMAPV5HEADER bmi = {0}; 141 bmi.bV5Size = sizeof(bmi); 142 bmi.bV5Width = width; 143 bmi.bV5Height = -height; // request a top-down bitmap. 144 bmi.bV5Planes = 1; 145 bmi.bV5BitCount = kBytesPerPixel * 8; 146 bmi.bV5Compression = BI_RGB; 147 bmi.bV5AlphaMask = 0xff000000; 148 bmi.bV5CSType = LCS_WINDOWS_COLOR_SPACE; 149 bmi.bV5Intent = LCS_GM_BUSINESS; 150 if (!GetDIBits(dc, scoped_mask, 0, height, mask_data.get(), 151 reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS)) { 152 RTC_LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = " 153 << GetLastError(); 154 return NULL; 155 } 156 157 uint32_t* mask_plane = mask_data.get(); 158 std::unique_ptr<DesktopFrame> image( 159 new BasicDesktopFrame(DesktopSize(width, height), FOURCC_ARGB)); 160 bool has_alpha = false; 161 162 if (is_color) { 163 image.reset(new BasicDesktopFrame(DesktopSize(width, height))); 164 // Get the pixels from the color bitmap. 165 if (!GetDIBits(dc, scoped_color, 0, height, image->data(), 166 reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS)) { 167 RTC_LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = " 168 << GetLastError(); 169 return NULL; 170 } 171 172 // GetDIBits() does not provide any indication whether the bitmap has alpha 173 // channel, so we use HasAlphaChannel() below to find it out. 174 has_alpha = HasAlphaChannel(reinterpret_cast<uint32_t*>(image->data()), 175 width, width, height); 176 } else { 177 // For non-color cursors, the mask contains both an AND and an XOR mask and 178 // the height includes both. Thus, the width is correct, but we need to 179 // divide by 2 to get the correct mask height. 180 height /= 2; 181 182 image.reset(new BasicDesktopFrame(DesktopSize(width, height))); 183 184 // The XOR mask becomes the color bitmap. 185 memcpy(image->data(), mask_plane + (width * height), 186 image->stride() * height); 187 } 188 189 // Reconstruct transparency from the mask if the color image does not has 190 // alpha channel. 191 if (!has_alpha) { 192 bool add_outline = false; 193 uint32_t* dst = reinterpret_cast<uint32_t*>(image->data()); 194 uint32_t* mask = mask_plane; 195 for (int y = 0; y < height; y++) { 196 for (int x = 0; x < width; x++) { 197 // The two bitmaps combine as follows: 198 // mask color Windows Result Our result RGB Alpha 199 // 0 00 Black Black 00 ff 200 // 0 ff White White ff ff 201 // 1 00 Screen Transparent 00 00 202 // 1 ff Reverse-screen Black 00 ff 203 // 204 // Since we don't support XOR cursors, we replace the "Reverse Screen" 205 // with black. In this case, we also add an outline around the cursor 206 // so that it is visible against a dark background. 207 if (*mask == kPixelRgbWhite) { 208 if (*dst != 0) { 209 add_outline = true; 210 *dst = kPixelRgbaBlack; 211 } else { 212 *dst = kPixelRgbaTransparent; 213 } 214 } else { 215 *dst = kPixelRgbaBlack ^ *dst; 216 } 217 218 ++dst; 219 ++mask; 220 } 221 } 222 if (add_outline) { 223 AddCursorOutline(width, height, 224 reinterpret_cast<uint32_t*>(image->data())); 225 } 226 } 227 228 // Pre-multiply the resulting pixels since MouseCursor uses premultiplied 229 // images. 230 AlphaMul(reinterpret_cast<uint32_t*>(image->data()), width, height); 231 232 return new MouseCursor(image.release(), DesktopVector(hotspot_x, hotspot_y)); 233 } 234 235 } // namespace webrtc