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