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/base/scoped_ptr.h" 16 #include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h" 17 #include "webrtc/modules/desktop_capture/desktop_frame.h" 18 #include "webrtc/modules/desktop_capture/desktop_geometry.h" 19 #include "webrtc/modules/desktop_capture/mouse_cursor.h" 20 #include "webrtc/system_wrappers/include/logging.h" 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); 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 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) { 79 static_assert(sizeof(uint32_t) == kBytesPerPixel, 80 "size of uint32 should be the number of bytes per pixel"); 81 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 } 92 } 93 94 // Scans a 32bpp bitmap looking for any pixels with non-zero alpha component. 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) { 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) { 100 if (plane->rgbReserved != 0) 101 return true; 102 plane += 1; 103 } 104 plane += stride - width; 105 } 106 107 return false; 108 } 109 110 } // namespace 111 112 MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) { 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; 138 rtc::scoped_ptr<uint32_t[]> mask_data(new uint32_t[width * height]); 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(); 165 rtc::scoped_ptr<DesktopFrame> image( 166 new BasicDesktopFrame(DesktopSize(width, height))); 167 bool has_alpha = false; 168 169 if (is_color) { 170 image.reset(new BasicDesktopFrame(DesktopSize(width, height))); 171 // Get the pixels from the color bitmap. 172 if (!GetDIBits(dc, 173 scoped_color, 174 0, 175 height, 176 image->data(), 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 184 // GetDIBits() does not provide any indication whether the bitmap has alpha 185 // channel, so we use HasAlphaChannel() below to find it out. 186 has_alpha = HasAlphaChannel(reinterpret_cast<uint32_t*>(image->data()), 187 width, width, height); 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 194 image.reset(new BasicDesktopFrame(DesktopSize(width, height))); 195 196 // The XOR mask becomes the color bitmap. 197 memcpy( 198 image->data(), mask_plane + (width * height), image->stride() * height); 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; 205 uint32_t* dst = reinterpret_cast<uint32_t*>(image->data()); 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) { 220 if (*dst != 0) { 221 add_outline = true; 222 *dst = kPixelRgbaBlack; 223 } else { 224 *dst = kPixelRgbaTransparent; 225 } 226 } else { 227 *dst = kPixelRgbaBlack ^ *dst; 228 } 229 230 ++dst; 231 ++mask; 232 } 233 } 234 if (add_outline) { 235 AddCursorOutline( 236 width, height, reinterpret_cast<uint32_t*>(image->data())); 237 } 238 } 239 240 // Pre-multiply the resulting pixels since MouseCursor uses premultiplied 241 // images. 242 AlphaMul(reinterpret_cast<uint32_t*>(image->data()), width, height); 243 244 return new MouseCursor( 245 image.release(), DesktopVector(hotspot_x, hotspot_y)); 246 } 247 248 } // namespace webrtc 249