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