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/interface/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 const uint32_t kPixelRgbBlack = RGB(0, 0, 0); 54 55 // Expands the cursor shape to add a white outline for visibility against 56 // dark backgrounds. 57 void AddCursorOutline(int width, int height, uint32_t* data) { 58 for (int y = 0; y < height; y++) { 59 for (int x = 0; x < width; x++) { 60 // If this is a transparent pixel (bgr == 0 and alpha = 0), check the 61 // neighbor pixels to see if this should be changed to an outline pixel. 62 if (*data == kPixelRgbaTransparent) { 63 // Change to white pixel if any neighbors (top, bottom, left, right) 64 // are black. 65 if ((y > 0 && data[-width] == kPixelRgbaBlack) || 66 (y < height - 1 && data[width] == kPixelRgbaBlack) || 67 (x > 0 && data[-1] == kPixelRgbaBlack) || 68 (x < width - 1 && data[1] == kPixelRgbaBlack)) { 69 *data = kPixelRgbaWhite; 70 } 71 } 72 data++; 73 } 74 } 75 } 76 77 // Premultiplies RGB components of the pixel data in the given image by 78 // the corresponding alpha components. 79 void AlphaMul(uint32_t* data, int width, int height) { 80 static_assert(sizeof(uint32_t) == kBytesPerPixel, 81 "size of uint32 should be the number of bytes per pixel"); 82 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 } 93 } 94 95 // Scans a 32bpp bitmap looking for any pixels with non-zero alpha component. 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) { 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) { 101 if (plane->rgbReserved != 0) 102 return true; 103 plane += 1; 104 } 105 plane += stride - width; 106 } 107 108 return false; 109 } 110 111 } // namespace 112 113 MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) { 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 rtc::scoped_ptr<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(); 166 rtc::scoped_ptr<DesktopFrame> image( 167 new BasicDesktopFrame(DesktopSize(width, height))); 168 bool has_alpha = false; 169 170 if (is_color) { 171 image.reset(new BasicDesktopFrame(DesktopSize(width, height))); 172 // Get the pixels from the color bitmap. 173 if (!GetDIBits(dc, 174 scoped_color, 175 0, 176 height, 177 image->data(), 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 185 // GetDIBits() does not provide any indication whether the bitmap has alpha 186 // channel, so we use HasAlphaChannel() below to find it out. 187 has_alpha = HasAlphaChannel(reinterpret_cast<uint32_t*>(image->data()), 188 width, width, height); 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 195 image.reset(new BasicDesktopFrame(DesktopSize(width, height))); 196 197 // The XOR mask becomes the color bitmap. 198 memcpy( 199 image->data(), mask_plane + (width * height), image->stride() * height); 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; 206 uint32_t* dst = reinterpret_cast<uint32_t*>(image->data()); 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) { 221 if (*dst != 0) { 222 add_outline = true; 223 *dst = kPixelRgbaBlack; 224 } else { 225 *dst = kPixelRgbaTransparent; 226 } 227 } else { 228 *dst = kPixelRgbaBlack ^ *dst; 229 } 230 231 ++dst; 232 ++mask; 233 } 234 } 235 if (add_outline) { 236 AddCursorOutline( 237 width, height, reinterpret_cast<uint32_t*>(image->data())); 238 } 239 } 240 241 // Pre-multiply the resulting pixels since MouseCursor uses premultiplied 242 // images. 243 AlphaMul(reinterpret_cast<uint32_t*>(image->data()), width, height); 244 245 return new MouseCursor( 246 image.release(), DesktopVector(hotspot_x, hotspot_y)); 247 } 248 249 } // namespace webrtc 250