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