// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "gfx/canvas_direct2d.h" #include "base/scoped_ptr.h" #include "gfx/rect.h" namespace { // Converts a SkColor to a ColorF. D2D1_COLOR_F SkColorToColorF(SkColor color) { return D2D1::ColorF(static_cast(SkColorGetR(color)) / 0xFF, static_cast(SkColorGetG(color)) / 0xFF, static_cast(SkColorGetB(color)) / 0xFF, static_cast(SkColorGetA(color)) / 0xFF); } D2D1_RECT_F RectToRectF(int x, int y, int w, int h) { return D2D1::RectF(static_cast(x), static_cast(y), static_cast(x + w), static_cast(y + h)); } D2D1_RECT_F RectToRectF(const gfx::Rect& rect) { return RectToRectF(rect.x(), rect.y(), rect.width(), rect.height()); } D2D1_POINT_2F PointToPoint2F(const gfx::Point& point) { return D2D1::Point2F(static_cast(point.x()), static_cast(point.y())); } D2D1_POINT_2F PointToPoint2F(int x, int y) { return D2D1::Point2F(static_cast(x), static_cast(y)); } D2D1_EXTEND_MODE TileModeToExtendMode(gfx::Canvas::TileMode tile_mode) { switch (tile_mode) { case gfx::Canvas::TileMode_Clamp: return D2D1_EXTEND_MODE_CLAMP; case gfx::Canvas::TileMode_Mirror: return D2D1_EXTEND_MODE_MIRROR; case gfx::Canvas::TileMode_Repeat: return D2D1_EXTEND_MODE_WRAP; default: NOTREACHED() << "Invalid TileMode"; } return D2D1_EXTEND_MODE_CLAMP; } D2D1_BITMAP_INTERPOLATION_MODE FilterToInterpolationMode(bool filter) { return filter ? D2D1_BITMAP_INTERPOLATION_MODE_LINEAR : D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; } // Creates a Direct2D gradient stop collection for the specified colors and // positions. The caller is responsible for releasing this object. ID2D1GradientStopCollection* CreateGradientStopCollection( ID2D1RenderTarget* render_target, const SkColor colors[], const float positions[], size_t position_count, gfx::Canvas::TileMode tile_mode) { scoped_array gradient_stops( new D2D1_GRADIENT_STOP[position_count]); for (size_t i = 0; i < position_count; ++i) { gradient_stops[i].color = SkColorToColorF(colors[i]); gradient_stops[i].position = positions[i]; } ID2D1GradientStopCollection* gradient_stop_collection = NULL; HRESULT hr = render_target->CreateGradientStopCollection( gradient_stops.get(), position_count, D2D1_GAMMA_2_2, TileModeToExtendMode(tile_mode), &gradient_stop_collection); return SUCCEEDED(hr) ? gradient_stop_collection : NULL; } // Creates a Direct2D bitmap object from the contents of a SkBitmap. The caller // is responsible for releasing this object. ID2D1Bitmap* CreateD2D1BitmapFromSkBitmap(ID2D1RenderTarget* render_target, const SkBitmap& bitmap) { ID2D1Bitmap* d2d1_bitmap = NULL; HRESULT hr = render_target->CreateBitmap( D2D1::SizeU(bitmap.width(), bitmap.height()), NULL, NULL, D2D1::BitmapProperties( D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)), &d2d1_bitmap); if (FAILED(hr)) return NULL; bitmap.lockPixels(); d2d1_bitmap->CopyFromMemory(NULL, bitmap.getPixels(), bitmap.rowBytes()); bitmap.unlockPixels(); return d2d1_bitmap; } // Creates a Direct2D bitmap brush from the contents of a SkBitmap. The caller // is responsible for releasing this object. ID2D1Brush* CreateD2D1BrushFromSkBitmap(ID2D1RenderTarget* render_target, const SkBitmap& bitmap, gfx::Canvas::TileMode tile_mode_x, gfx::Canvas::TileMode tile_mode_y) { ScopedComPtr d2d1_bitmap( CreateD2D1BitmapFromSkBitmap(render_target, bitmap)); ID2D1BitmapBrush* brush = NULL; render_target->CreateBitmapBrush( d2d1_bitmap, D2D1::BitmapBrushProperties(TileModeToExtendMode(tile_mode_x), TileModeToExtendMode(tile_mode_y)), D2D1::BrushProperties(), &brush); return brush; } // A platform wrapper for a Direct2D brush that makes sure the underlying // ID2D1Brush COM object is released when this object is destroyed. class Direct2DBrush : public gfx::Brush { public: explicit Direct2DBrush(ID2D1Brush* brush) : brush_(brush) { } ID2D1Brush* brush() const { return brush_.get(); } private: ScopedComPtr brush_; DISALLOW_COPY_AND_ASSIGN(Direct2DBrush); }; } // namespace namespace gfx { // static ID2D1Factory* CanvasDirect2D::d2d1_factory_ = NULL; //////////////////////////////////////////////////////////////////////////////// // CanvasDirect2D, public: CanvasDirect2D::CanvasDirect2D(ID2D1RenderTarget* rt) : rt_(rt) { // A RenderState entry is pushed onto the stack to track the clip count prior // to any calls to Save*(). state_.push(RenderState()); rt_->BeginDraw(); } CanvasDirect2D::~CanvasDirect2D() { // Unwind any clips that were pushed outside of any Save*()/Restore() pairs. int clip_count = state_.top().clip_count; for (int i = 0; i < clip_count; ++i) rt_->PopAxisAlignedClip(); rt_->EndDraw(); } // static ID2D1Factory* CanvasDirect2D::GetD2D1Factory() { if (!d2d1_factory_) D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2d1_factory_); return d2d1_factory_; } //////////////////////////////////////////////////////////////////////////////// // CanvasDirect2D, Canvas implementation: void CanvasDirect2D::Save() { SaveInternal(NULL); } void CanvasDirect2D::SaveLayerAlpha(uint8 alpha) { SaveLayerAlpha(alpha, gfx::Rect()); } void CanvasDirect2D::SaveLayerAlpha(uint8 alpha, const gfx::Rect& layer_bounds) { D2D1_RECT_F bounds = D2D1::InfiniteRect(); if (!layer_bounds.IsEmpty()) bounds = RectToRectF(layer_bounds); ID2D1Layer* layer = NULL; HRESULT hr = rt_->CreateLayer(NULL, &layer); if (SUCCEEDED(hr)) { rt_->PushLayer(D2D1::LayerParameters(bounds, NULL, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1::IdentityMatrix(), static_cast(alpha) / 0xFF, NULL, D2D1_LAYER_OPTIONS_NONE), layer); } SaveInternal(layer); } void CanvasDirect2D::Restore() { ID2D1Layer* layer = state_.top().layer; if (layer) { rt_->PopLayer(); layer->Release(); } int clip_count = state_.top().clip_count; for (int i = 0; i < clip_count; ++i) rt_->PopAxisAlignedClip(); state_.pop(); // The state_ stack should never be empty - we should always have at least one // entry to hold a clip count when there is no active save/restore entry. CHECK(!state_.empty()) << "Called Restore() once too often!"; rt_->RestoreDrawingState(drawing_state_block_); } bool CanvasDirect2D::ClipRectInt(int x, int y, int w, int h) { rt_->PushAxisAlignedClip(RectToRectF(x, y, w, h), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); // Increment the clip count so the call to PushAxisAlignedClip() can be // balanced with a call to PopAxisAlignedClip in the next Restore(). ++state_.top().clip_count; return w > 0 && h > 0; } void CanvasDirect2D::TranslateInt(int x, int y) { D2D1_MATRIX_3X2_F raw; rt_->GetTransform(&raw); D2D1::Matrix3x2F transform(raw._11, raw._12, raw._21, raw._22, raw._31, raw._32); transform = D2D1::Matrix3x2F::Translation(static_cast(x), static_cast(y)) * transform; rt_->SetTransform(transform); } void CanvasDirect2D::ScaleInt(int x, int y) { D2D1_MATRIX_3X2_F raw; rt_->GetTransform(&raw); D2D1::Matrix3x2F transform(raw._11, raw._12, raw._21, raw._22, raw._31, raw._32); transform = D2D1::Matrix3x2F::Scale(static_cast(x), static_cast(y)) * transform; rt_->SetTransform(transform); } void CanvasDirect2D::FillRectInt(int x, int y, int w, int h, const SkPaint& paint) { NOTIMPLEMENTED(); } void CanvasDirect2D::FillRectInt(const SkColor& color, int x, int y, int w, int h) { ScopedComPtr solid_brush; rt_->CreateSolidColorBrush(SkColorToColorF(color), solid_brush.Receive()); rt_->FillRectangle(RectToRectF(x, y, w, h), solid_brush); } void CanvasDirect2D::FillRectInt(const gfx::Brush* brush, int x, int y, int w, int h) { const Direct2DBrush* d2d_brush = static_cast(brush); rt_->FillRectangle(RectToRectF(x, y, w, h), d2d_brush->brush()); } void CanvasDirect2D::DrawRectInt(const SkColor& color, int x, int y, int w, int h) { ScopedComPtr solid_brush; rt_->CreateSolidColorBrush(SkColorToColorF(color), solid_brush.Receive()); rt_->DrawRectangle(RectToRectF(x, y, w, h), solid_brush); } void CanvasDirect2D::DrawRectInt(const SkColor& color, int x, int y, int w, int h, SkXfermode::Mode mode) { NOTIMPLEMENTED(); } void CanvasDirect2D::DrawLineInt(const SkColor& color, int x1, int y1, int x2, int y2) { ScopedComPtr solid_brush; rt_->CreateSolidColorBrush(SkColorToColorF(color), solid_brush.Receive()); rt_->DrawLine(PointToPoint2F(x1, y1), PointToPoint2F(x2, y2), solid_brush); } void CanvasDirect2D::DrawBitmapInt(const SkBitmap& bitmap, int x, int y) { ScopedComPtr d2d1_bitmap( CreateD2D1BitmapFromSkBitmap(rt_, bitmap)); rt_->DrawBitmap(d2d1_bitmap, RectToRectF(x, y, bitmap.width(), bitmap.height()), 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, RectToRectF(0, 0, bitmap.width(), bitmap.height())); } void CanvasDirect2D::DrawBitmapInt(const SkBitmap& bitmap, int x, int y, const SkPaint& paint) { NOTIMPLEMENTED(); } void CanvasDirect2D::DrawBitmapInt(const SkBitmap& bitmap, int src_x, int src_y, int src_w, int src_h, int dest_x, int dest_y, int dest_w, int dest_h, bool filter) { ScopedComPtr d2d1_bitmap( CreateD2D1BitmapFromSkBitmap(rt_, bitmap)); rt_->DrawBitmap(d2d1_bitmap, RectToRectF(dest_x, dest_y, dest_w, dest_h), 1.0f, FilterToInterpolationMode(filter), RectToRectF(src_x, src_y, src_w, src_h)); } void CanvasDirect2D::DrawBitmapInt(const SkBitmap& bitmap, int src_x, int src_y, int src_w, int src_h, int dest_x, int dest_y, int dest_w, int dest_h, bool filter, const SkPaint& paint) { NOTIMPLEMENTED(); } void CanvasDirect2D::DrawStringInt(const std::wstring& text, const gfx::Font& font, const SkColor& color, int x, int y, int w, int h) { NOTIMPLEMENTED(); } void CanvasDirect2D::DrawStringInt(const std::wstring& text, const gfx::Font& font, const SkColor& color, const gfx::Rect& display_rect) { NOTIMPLEMENTED(); } void CanvasDirect2D::DrawStringInt(const std::wstring& text, const gfx::Font& font, const SkColor& color, int x, int y, int w, int h, int flags) { NOTIMPLEMENTED(); } void CanvasDirect2D::DrawFocusRect(int x, int y, int width, int height) { NOTIMPLEMENTED(); } void CanvasDirect2D::TileImageInt(const SkBitmap& bitmap, int x, int y, int w, int h) { ScopedComPtr brush( CreateD2D1BrushFromSkBitmap(rt_, bitmap, TileMode_Repeat, TileMode_Repeat)); rt_->FillRectangle(RectToRectF(x, y, w, h), brush); } void CanvasDirect2D::TileImageInt(const SkBitmap& bitmap, int src_x, int src_y, int dest_x, int dest_y, int w, int h) { NOTIMPLEMENTED(); } gfx::NativeDrawingContext CanvasDirect2D::BeginPlatformPaint() { DCHECK(!interop_rt_.get()); interop_rt_.QueryFrom(rt_); HDC dc = NULL; if (interop_rt_.get()) interop_rt_->GetDC(D2D1_DC_INITIALIZE_MODE_COPY, &dc); return dc; } void CanvasDirect2D::EndPlatformPaint() { DCHECK(interop_rt_.get()); interop_rt_->ReleaseDC(NULL); interop_rt_.release(); } Brush* CanvasDirect2D::CreateLinearGradientBrush( const gfx::Point& start_point, const gfx::Point& end_point, const SkColor colors[], const float positions[], size_t position_count, TileMode tile_mode) { ScopedComPtr gradient_stop_collection( CreateGradientStopCollection(rt_, colors, positions, position_count, tile_mode)); if (!gradient_stop_collection.get()) return NULL; ID2D1LinearGradientBrush* brush = NULL; HRESULT hr = rt_->CreateLinearGradientBrush( D2D1::LinearGradientBrushProperties(PointToPoint2F(start_point), PointToPoint2F(end_point)), gradient_stop_collection, &brush); return SUCCEEDED(hr) ? new Direct2DBrush(brush) : NULL; } Brush* CanvasDirect2D::CreateRadialGradientBrush( const gfx::Point& center_point, float radius, const SkColor colors[], const float positions[], size_t position_count, TileMode tile_mode) { ScopedComPtr gradient_stop_collection( CreateGradientStopCollection(rt_, colors, positions, position_count, tile_mode)); if (!gradient_stop_collection.get()) return NULL; ID2D1RadialGradientBrush* brush = NULL; HRESULT hr = rt_->CreateRadialGradientBrush( D2D1::RadialGradientBrushProperties(PointToPoint2F(center_point), PointToPoint2F(gfx::Point()), radius, radius), gradient_stop_collection, &brush); return SUCCEEDED(hr) ? new Direct2DBrush(brush) : NULL; } Brush* CanvasDirect2D::CreateBitmapBrush( const SkBitmap& bitmap, TileMode tile_mode_x, TileMode tile_mode_y) { ID2D1Brush* brush = CreateD2D1BrushFromSkBitmap(rt_, bitmap, tile_mode_x, tile_mode_y); return brush ? new Direct2DBrush(brush) : NULL; } CanvasSkia* CanvasDirect2D::AsCanvasSkia() { return NULL; } const CanvasSkia* CanvasDirect2D::AsCanvasSkia() const { return NULL; } //////////////////////////////////////////////////////////////////////////////// // CanvasDirect2D, private: void CanvasDirect2D::SaveInternal(ID2D1Layer* layer) { if (!drawing_state_block_) GetD2D1Factory()->CreateDrawingStateBlock(drawing_state_block_.Receive()); rt_->SaveDrawingState(drawing_state_block_.get()); state_.push(RenderState(layer)); } } // namespace gfx