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