OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "base/gfx/vector_device.h" | |
6 | |
7 #include "base/gfx/gdi_util.h" | |
8 #include "base/gfx/skia_utils.h" | |
9 #include "base/logging.h" | |
10 #include "base/scoped_handle.h" | |
11 | |
12 #include "SkUtils.h" | |
13 | |
14 namespace gfx { | |
15 | |
16 VectorDevice* VectorDevice::create(HDC dc, int width, int height) { | |
17 InitializeDC(dc); | |
18 | |
19 // Link the SkBitmap to the current selected bitmap in the device context. | |
20 SkBitmap bitmap; | |
21 HGDIOBJ selected_bitmap = GetCurrentObject(dc, OBJ_BITMAP); | |
22 bool succeeded = false; | |
23 if (selected_bitmap != NULL) { | |
24 BITMAP bitmap_data; | |
25 if (GetObject(selected_bitmap, sizeof(BITMAP), &bitmap_data) == | |
26 sizeof(BITMAP)) { | |
27 // The context has a bitmap attached. Attach our SkBitmap to it. | |
28 // Warning: If the bitmap gets unselected from the HDC, VectorDevice has | |
29 // no way to detect this, so the HBITMAP could be released while SkBitmap | |
30 // still has a reference to it. Be cautious. | |
31 if (width == bitmap_data.bmWidth && | |
32 height == bitmap_data.bmHeight) { | |
33 bitmap.setConfig(SkBitmap::kARGB_8888_Config, | |
34 bitmap_data.bmWidth, | |
35 bitmap_data.bmHeight, | |
36 bitmap_data.bmWidthBytes); | |
37 bitmap.setPixels(bitmap_data.bmBits); | |
38 succeeded = true; | |
39 } | |
40 } | |
41 } | |
42 | |
43 if (!succeeded) | |
44 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); | |
45 | |
46 return new VectorDevice(dc, bitmap); | |
47 } | |
48 | |
49 VectorDevice::VectorDevice(HDC dc, const SkBitmap& bitmap) | |
50 : PlatformDeviceWin(bitmap), | |
51 hdc_(dc), | |
52 previous_brush_(NULL), | |
53 previous_pen_(NULL) { | |
54 transform_.reset(); | |
55 } | |
56 | |
57 VectorDevice::~VectorDevice() { | |
58 DCHECK(previous_brush_ == NULL); | |
59 DCHECK(previous_pen_ == NULL); | |
60 } | |
61 | |
62 | |
63 void VectorDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) { | |
64 // TODO(maruel): Bypass the current transformation matrix. | |
65 SkRect rect; | |
66 rect.fLeft = 0; | |
67 rect.fTop = 0; | |
68 rect.fRight = SkIntToScalar(width() + 1); | |
69 rect.fBottom = SkIntToScalar(height() + 1); | |
70 drawRect(draw, rect, paint); | |
71 } | |
72 | |
73 void VectorDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, | |
74 size_t count, const SkPoint pts[], | |
75 const SkPaint& paint) { | |
76 if (!count) | |
77 return; | |
78 | |
79 if (mode == SkCanvas::kPoints_PointMode) { | |
80 NOTREACHED(); | |
81 return; | |
82 } | |
83 | |
84 SkPaint tmp_paint(paint); | |
85 tmp_paint.setStyle(SkPaint::kStroke_Style); | |
86 | |
87 // Draw a path instead. | |
88 SkPath path; | |
89 switch (mode) { | |
90 case SkCanvas::kLines_PointMode: | |
91 if (count % 2) { | |
92 NOTREACHED(); | |
93 return; | |
94 } | |
95 for (size_t i = 0; i < count / 2; ++i) { | |
96 path.moveTo(pts[2 * i]); | |
97 path.lineTo(pts[2 * i + 1]); | |
98 } | |
99 break; | |
100 case SkCanvas::kPolygon_PointMode: | |
101 path.moveTo(pts[0]); | |
102 for (size_t i = 1; i < count; ++i) { | |
103 path.lineTo(pts[i]); | |
104 } | |
105 break; | |
106 default: | |
107 NOTREACHED(); | |
108 return; | |
109 } | |
110 // Draw the calculated path. | |
111 drawPath(draw, path, tmp_paint); | |
112 } | |
113 | |
114 void VectorDevice::drawRect(const SkDraw& draw, const SkRect& rect, | |
115 const SkPaint& paint) { | |
116 if (paint.getPathEffect()) { | |
117 // Draw a path instead. | |
118 SkPath path_orginal; | |
119 path_orginal.addRect(rect); | |
120 | |
121 // Apply the path effect to the rect. | |
122 SkPath path_modified; | |
123 paint.getFillPath(path_orginal, &path_modified); | |
124 | |
125 // Removes the path effect from the temporary SkPaint object. | |
126 SkPaint paint_no_effet(paint); | |
127 paint_no_effet.setPathEffect(NULL)->safeUnref(); | |
128 | |
129 // Draw the calculated path. | |
130 drawPath(draw, path_modified, paint_no_effet); | |
131 return; | |
132 } | |
133 | |
134 if (!ApplyPaint(paint)) { | |
135 return; | |
136 } | |
137 HDC dc = getBitmapDC(); | |
138 if (!Rectangle(dc, SkScalarRound(rect.fLeft), | |
139 SkScalarRound(rect.fTop), | |
140 SkScalarRound(rect.fRight), | |
141 SkScalarRound(rect.fBottom))) { | |
142 NOTREACHED(); | |
143 } | |
144 Cleanup(); | |
145 } | |
146 | |
147 void VectorDevice::drawPath(const SkDraw& draw, const SkPath& path, | |
148 const SkPaint& paint) { | |
149 if (paint.getPathEffect()) { | |
150 // Apply the path effect forehand. | |
151 SkPath path_modified; | |
152 paint.getFillPath(path, &path_modified); | |
153 | |
154 // Removes the path effect from the temporary SkPaint object. | |
155 SkPaint paint_no_effet(paint); | |
156 paint_no_effet.setPathEffect(NULL)->safeUnref(); | |
157 | |
158 // Draw the calculated path. | |
159 drawPath(draw, path_modified, paint_no_effet); | |
160 return; | |
161 } | |
162 | |
163 if (!ApplyPaint(paint)) { | |
164 return; | |
165 } | |
166 HDC dc = getBitmapDC(); | |
167 PlatformDeviceWin::LoadPathToDC(dc, path); | |
168 switch (paint.getStyle()) { | |
169 case SkPaint::kFill_Style: { | |
170 BOOL res = StrokeAndFillPath(dc); | |
171 DCHECK(res != 0); | |
172 break; | |
173 } | |
174 case SkPaint::kStroke_Style: { | |
175 BOOL res = StrokePath(dc); | |
176 DCHECK(res != 0); | |
177 break; | |
178 } | |
179 case SkPaint::kStrokeAndFill_Style: { | |
180 BOOL res = StrokeAndFillPath(dc); | |
181 DCHECK(res != 0); | |
182 break; | |
183 } | |
184 default: | |
185 NOTREACHED(); | |
186 break; | |
187 } | |
188 Cleanup(); | |
189 } | |
190 | |
191 void VectorDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap, | |
192 const SkMatrix& matrix, const SkPaint& paint) { | |
193 // Load the temporary matrix. This is what will translate, rotate and resize | |
194 // the bitmap. | |
195 SkMatrix actual_transform(transform_); | |
196 actual_transform.preConcat(matrix); | |
197 LoadTransformToDC(hdc_, actual_transform); | |
198 | |
199 InternalDrawBitmap(bitmap, 0, 0, paint); | |
200 | |
201 // Restore the original matrix. | |
202 LoadTransformToDC(hdc_, transform_); | |
203 } | |
204 | |
205 void VectorDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap, | |
206 int x, int y, const SkPaint& paint) { | |
207 SkMatrix identity; | |
208 identity.reset(); | |
209 LoadTransformToDC(hdc_, identity); | |
210 | |
211 InternalDrawBitmap(bitmap, x, y, paint); | |
212 | |
213 // Restore the original matrix. | |
214 LoadTransformToDC(hdc_, transform_); | |
215 } | |
216 | |
217 void VectorDevice::drawText(const SkDraw& draw, const void* text, size_t byteLen
gth, | |
218 SkScalar x, SkScalar y, const SkPaint& paint) { | |
219 // This function isn't used in the code. Verify this assumption. | |
220 NOTREACHED(); | |
221 } | |
222 | |
223 void VectorDevice::drawPosText(const SkDraw& draw, const void* text, size_t len, | |
224 const SkScalar pos[], SkScalar constY, | |
225 int scalarsPerPos, const SkPaint& paint) { | |
226 // This function isn't used in the code. Verify this assumption. | |
227 NOTREACHED(); | |
228 } | |
229 | |
230 void VectorDevice::drawTextOnPath(const SkDraw& draw, const void* text, | |
231 size_t len, | |
232 const SkPath& path, const SkMatrix* matrix, | |
233 const SkPaint& paint) { | |
234 // This function isn't used in the code. Verify this assumption. | |
235 NOTREACHED(); | |
236 } | |
237 | |
238 void VectorDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode, | |
239 int vertexCount, | |
240 const SkPoint vertices[], const SkPoint texs[], | |
241 const SkColor colors[], SkXfermode* xmode, | |
242 const uint16_t indices[], int indexCount, | |
243 const SkPaint& paint) { | |
244 // This function isn't used in the code. Verify this assumption. | |
245 NOTREACHED(); | |
246 } | |
247 | |
248 void VectorDevice::drawDevice(const SkDraw& draw, SkDevice* device, int x, | |
249 int y, const SkPaint& paint) { | |
250 // TODO(maruel): http://b/1183870 Playback the EMF buffer at printer's dpi if | |
251 // it is a vectorial device. | |
252 drawSprite(draw, device->accessBitmap(false), x, y, paint); | |
253 } | |
254 | |
255 bool VectorDevice::ApplyPaint(const SkPaint& paint) { | |
256 // Note: The goal here is to transfert the SkPaint's state to the HDC's state. | |
257 // This function does not execute the SkPaint drawing commands. These should | |
258 // be executed in drawPaint(). | |
259 | |
260 SkPaint::Style style = paint.getStyle(); | |
261 if (!paint.getAlpha()) | |
262 style = SkPaint::kStyleCount; | |
263 | |
264 switch (style) { | |
265 case SkPaint::kFill_Style: | |
266 if (!CreateBrush(true, paint) || | |
267 !CreatePen(false, paint)) | |
268 return false; | |
269 break; | |
270 case SkPaint::kStroke_Style: | |
271 if (!CreateBrush(false, paint) || | |
272 !CreatePen(true, paint)) | |
273 return false; | |
274 break; | |
275 case SkPaint::kStrokeAndFill_Style: | |
276 if (!CreateBrush(true, paint) || | |
277 !CreatePen(true, paint)) | |
278 return false; | |
279 break; | |
280 default: | |
281 if (!CreateBrush(false, paint) || | |
282 !CreatePen(false, paint)) | |
283 return false; | |
284 break; | |
285 } | |
286 | |
287 /* | |
288 getFlags(); | |
289 isAntiAlias(); | |
290 isDither() | |
291 isLinearText() | |
292 isSubpixelText() | |
293 isUnderlineText() | |
294 isStrikeThruText() | |
295 isFakeBoldText() | |
296 isDevKernText() | |
297 isFilterBitmap() | |
298 | |
299 // Skia's text is not used. This should be fixed. | |
300 getTextAlign() | |
301 getTextScaleX() | |
302 getTextSkewX() | |
303 getTextEncoding() | |
304 getFontMetrics() | |
305 getFontSpacing() | |
306 */ | |
307 | |
308 // BUG 1094907: Implement shaders. Shaders currently in use: | |
309 // SkShader::CreateBitmapShader | |
310 // SkGradientShader::CreateRadial | |
311 // SkGradientShader::CreateLinear | |
312 // DCHECK(!paint.getShader()); | |
313 | |
314 // http://b/1106647 Implement loopers and mask filter. Looper currently in | |
315 // use: | |
316 // SkBlurDrawLooper is used for shadows. | |
317 // DCHECK(!paint.getLooper()); | |
318 // DCHECK(!paint.getMaskFilter()); | |
319 | |
320 // http://b/1165900 Implement xfermode. | |
321 // DCHECK(!paint.getXfermode()); | |
322 | |
323 // The path effect should be processed before arriving here. | |
324 DCHECK(!paint.getPathEffect()); | |
325 | |
326 // These aren't used in the code. Verify this assumption. | |
327 DCHECK(!paint.getColorFilter()); | |
328 DCHECK(!paint.getRasterizer()); | |
329 // Reuse code to load Win32 Fonts. | |
330 DCHECK(!paint.getTypeface()); | |
331 return true; | |
332 } | |
333 | |
334 void VectorDevice::setMatrixClip(const SkMatrix& transform, | |
335 const SkRegion& region) { | |
336 transform_ = transform; | |
337 LoadTransformToDC(hdc_, transform_); | |
338 clip_region_ = region; | |
339 if (!clip_region_.isEmpty()) | |
340 LoadClipRegion(); | |
341 } | |
342 | |
343 void VectorDevice::drawToHDC(HDC dc, int x, int y, const RECT* src_rect) { | |
344 NOTREACHED(); | |
345 } | |
346 | |
347 void VectorDevice::LoadClipRegion() { | |
348 SkMatrix t; | |
349 t.reset(); | |
350 LoadClippingRegionToDC(hdc_, clip_region_, t); | |
351 } | |
352 | |
353 bool VectorDevice::CreateBrush(bool use_brush, COLORREF color) { | |
354 DCHECK(previous_brush_ == NULL); | |
355 // We can't use SetDCBrushColor() or DC_BRUSH when drawing to a EMF buffer. | |
356 // SetDCBrushColor() calls are not recorded at all and DC_BRUSH will use | |
357 // WHITE_BRUSH instead. | |
358 | |
359 if (!use_brush) { | |
360 // Set the transparency. | |
361 if (0 == SetBkMode(hdc_, TRANSPARENT)) { | |
362 NOTREACHED(); | |
363 return false; | |
364 } | |
365 | |
366 // Select the NULL brush. | |
367 previous_brush_ = SelectObject(GetStockObject(NULL_BRUSH)); | |
368 return previous_brush_ != NULL; | |
369 } | |
370 | |
371 // Set the opacity. | |
372 if (0 == SetBkMode(hdc_, OPAQUE)) { | |
373 NOTREACHED(); | |
374 return false; | |
375 } | |
376 | |
377 // Create and select the brush. | |
378 previous_brush_ = SelectObject(CreateSolidBrush(color)); | |
379 return previous_brush_ != NULL; | |
380 } | |
381 | |
382 bool VectorDevice::CreatePen(bool use_pen, COLORREF color, int stroke_width, | |
383 float stroke_miter, DWORD pen_style) { | |
384 DCHECK(previous_pen_ == NULL); | |
385 // We can't use SetDCPenColor() or DC_PEN when drawing to a EMF buffer. | |
386 // SetDCPenColor() calls are not recorded at all and DC_PEN will use BLACK_PEN | |
387 // instead. | |
388 | |
389 // No pen case | |
390 if (!use_pen) { | |
391 previous_pen_ = SelectObject(GetStockObject(NULL_PEN)); | |
392 return previous_pen_ != NULL; | |
393 } | |
394 | |
395 // Use the stock pen if the stroke width is 0. | |
396 if (stroke_width == 0) { | |
397 // Create a pen with the right color. | |
398 previous_pen_ = SelectObject(::CreatePen(PS_SOLID, 0, color)); | |
399 return previous_pen_ != NULL; | |
400 } | |
401 | |
402 // Load a custom pen. | |
403 LOGBRUSH brush; | |
404 brush.lbStyle = BS_SOLID; | |
405 brush.lbColor = color; | |
406 brush.lbHatch = 0; | |
407 HPEN pen = ExtCreatePen(pen_style, stroke_width, &brush, 0, NULL); | |
408 DCHECK(pen != NULL); | |
409 previous_pen_ = SelectObject(pen); | |
410 if (previous_pen_ == NULL) | |
411 return false; | |
412 | |
413 if (!SetMiterLimit(hdc_, stroke_miter, NULL)) { | |
414 NOTREACHED(); | |
415 return false; | |
416 } | |
417 return true; | |
418 } | |
419 | |
420 void VectorDevice::Cleanup() { | |
421 if (previous_brush_) { | |
422 HGDIOBJ result = SelectObject(previous_brush_); | |
423 previous_brush_ = NULL; | |
424 if (result) { | |
425 BOOL res = DeleteObject(result); | |
426 DCHECK(res != 0); | |
427 } | |
428 } | |
429 if (previous_pen_) { | |
430 HGDIOBJ result = SelectObject(previous_pen_); | |
431 previous_pen_ = NULL; | |
432 if (result) { | |
433 BOOL res = DeleteObject(result); | |
434 DCHECK(res != 0); | |
435 } | |
436 } | |
437 // Remove any loaded path from the context. | |
438 AbortPath(hdc_); | |
439 } | |
440 | |
441 HGDIOBJ VectorDevice::SelectObject(HGDIOBJ object) { | |
442 HGDIOBJ result = ::SelectObject(hdc_, object); | |
443 DCHECK(result != HGDI_ERROR); | |
444 if (result == HGDI_ERROR) | |
445 return NULL; | |
446 return result; | |
447 } | |
448 | |
449 bool VectorDevice::CreateBrush(bool use_brush, const SkPaint& paint) { | |
450 // Make sure that for transparent color, no brush is used. | |
451 if (paint.getAlpha() == 0) { | |
452 // Test if it ever happen. | |
453 NOTREACHED(); | |
454 use_brush = false; | |
455 } | |
456 | |
457 return CreateBrush(use_brush, SkColorToCOLORREF(paint.getColor())); | |
458 } | |
459 | |
460 bool VectorDevice::CreatePen(bool use_pen, const SkPaint& paint) { | |
461 // Make sure that for transparent color, no pen is used. | |
462 if (paint.getAlpha() == 0) { | |
463 // Test if it ever happen. | |
464 NOTREACHED(); | |
465 use_pen = false; | |
466 } | |
467 | |
468 DWORD pen_style = PS_GEOMETRIC | PS_SOLID; | |
469 switch (paint.getStrokeJoin()) { | |
470 case SkPaint::kMiter_Join: | |
471 // Connects path segments with a sharp join. | |
472 pen_style |= PS_JOIN_MITER; | |
473 break; | |
474 case SkPaint::kRound_Join: | |
475 // Connects path segments with a round join. | |
476 pen_style |= PS_JOIN_ROUND; | |
477 break; | |
478 case SkPaint::kBevel_Join: | |
479 // Connects path segments with a flat bevel join. | |
480 pen_style |= PS_JOIN_BEVEL; | |
481 break; | |
482 default: | |
483 NOTREACHED(); | |
484 break; | |
485 } | |
486 switch (paint.getStrokeCap()) { | |
487 case SkPaint::kButt_Cap: | |
488 // Begin/end contours with no extension. | |
489 pen_style |= PS_ENDCAP_FLAT; | |
490 break; | |
491 case SkPaint::kRound_Cap: | |
492 // Begin/end contours with a semi-circle extension. | |
493 pen_style |= PS_ENDCAP_ROUND; | |
494 break; | |
495 case SkPaint::kSquare_Cap: | |
496 // Begin/end contours with a half square extension. | |
497 pen_style |= PS_ENDCAP_SQUARE; | |
498 break; | |
499 default: | |
500 NOTREACHED(); | |
501 break; | |
502 } | |
503 | |
504 return CreatePen(use_pen, | |
505 SkColorToCOLORREF(paint.getColor()), | |
506 SkScalarRound(paint.getStrokeWidth()), | |
507 paint.getStrokeMiter(), | |
508 pen_style); | |
509 } | |
510 | |
511 void VectorDevice::InternalDrawBitmap(const SkBitmap& bitmap, int x, int y, | |
512 const SkPaint& paint) { | |
513 uint8 alpha = paint.getAlpha(); | |
514 if (alpha == 0) | |
515 return; | |
516 | |
517 bool is_translucent; | |
518 if (alpha != 255) { | |
519 // ApplyPaint expect an opaque color. | |
520 SkPaint tmp_paint(paint); | |
521 tmp_paint.setAlpha(255); | |
522 if (!ApplyPaint(tmp_paint)) | |
523 return; | |
524 is_translucent = true; | |
525 } else { | |
526 if (!ApplyPaint(paint)) | |
527 return; | |
528 is_translucent = false; | |
529 } | |
530 int src_size_x = bitmap.width(); | |
531 int src_size_y = bitmap.height(); | |
532 if (!src_size_x || !src_size_y) | |
533 return; | |
534 | |
535 // Create a BMP v4 header that we can serialize. | |
536 BITMAPV4HEADER bitmap_header; | |
537 gfx::CreateBitmapV4Header(src_size_x, src_size_y, &bitmap_header); | |
538 HDC dc = getBitmapDC(); | |
539 SkAutoLockPixels lock(bitmap); | |
540 DCHECK_EQ(bitmap.getConfig(), SkBitmap::kARGB_8888_Config); | |
541 const uint32_t* pixels = static_cast<const uint32_t*>(bitmap.getPixels()); | |
542 if (pixels == NULL) { | |
543 NOTREACHED(); | |
544 return; | |
545 } | |
546 | |
547 if (!is_translucent) { | |
548 int row_length = bitmap.rowBytesAsPixels(); | |
549 // There is no quick way to determine if an image is opaque. | |
550 for (int y2 = 0; y2 < src_size_y; ++y2) { | |
551 for (int x2 = 0; x2 < src_size_x; ++x2) { | |
552 if (SkColorGetA(pixels[(y2 * row_length) + x2]) != 255) { | |
553 is_translucent = true; | |
554 y2 = src_size_y; | |
555 break; | |
556 } | |
557 } | |
558 } | |
559 } | |
560 | |
561 BITMAPINFOHEADER hdr; | |
562 gfx::CreateBitmapHeader(src_size_x, src_size_y, &hdr); | |
563 if (is_translucent) { | |
564 // The image must be loaded as a bitmap inside a device context. | |
565 ScopedHDC bitmap_dc(::CreateCompatibleDC(dc)); | |
566 void* bits = NULL; | |
567 ScopedBitmap hbitmap(::CreateDIBSection( | |
568 bitmap_dc, reinterpret_cast<const BITMAPINFO*>(&hdr), | |
569 DIB_RGB_COLORS, &bits, NULL, 0)); | |
570 memcpy(bits, pixels, bitmap.getSize()); | |
571 DCHECK(hbitmap); | |
572 HGDIOBJ old_bitmap = ::SelectObject(bitmap_dc, hbitmap); | |
573 DeleteObject(old_bitmap); | |
574 | |
575 // After some analysis of IE7's behavior, this is the thing to do. I was | |
576 // sure IE7 was doing so kind of bitmasking due to the way translucent image | |
577 // where renderered but after some windbg tracing, it is being done by the | |
578 // printer driver after all (mostly HP printers). IE7 always use AlphaBlend | |
579 // for bitmasked images. The trick seems to switch the stretching mode in | |
580 // what the driver expects. | |
581 DWORD previous_mode = GetStretchBltMode(dc); | |
582 BOOL result = SetStretchBltMode(dc, COLORONCOLOR); | |
583 DCHECK(result); | |
584 // Note that this function expect premultiplied colors (!) | |
585 BLENDFUNCTION blend_function = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; | |
586 result = GdiAlphaBlend(dc, | |
587 x, y, // Destination origin. | |
588 src_size_x, src_size_y, // Destination size. | |
589 bitmap_dc, | |
590 0, 0, // Source origin. | |
591 src_size_x, src_size_y, // Source size. | |
592 blend_function); | |
593 DCHECK(result); | |
594 result = SetStretchBltMode(dc, previous_mode); | |
595 DCHECK(result); | |
596 } else { | |
597 BOOL result = StretchDIBits(dc, | |
598 x, y, // Destination origin. | |
599 src_size_x, src_size_y, | |
600 0, 0, // Source origin. | |
601 src_size_x, src_size_y, // Source size. | |
602 pixels, | |
603 reinterpret_cast<const BITMAPINFO*>(&hdr), | |
604 DIB_RGB_COLORS, | |
605 SRCCOPY); | |
606 DCHECK(result); | |
607 } | |
608 Cleanup(); | |
609 } | |
610 | |
611 } // namespace gfx | |
612 | |
OLD | NEW |