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