1 // Copyright (c) 2012 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.
5 #include "webkit/media/skcanvas_video_renderer.h"
7 #include "base/logging.h"
8 #include "media/base/video_frame.h"
9 #include "media/base/yuv_convert.h"
10 #include "third_party/skia/include/core/SkCanvas.h"
11 #include "third_party/skia/include/core/SkDevice.h"
13 namespace webkit_media
{
15 // CanFastPaint is a helper method to determine the conditions for fast
16 // painting. The conditions are:
17 // 1. No skew in canvas matrix.
18 // 2. No flipping nor mirroring.
19 // 3. Canvas has pixel format ARGB8888.
20 // 4. Canvas is opaque.
22 // TODO(hclam): The fast paint method should support flipping and mirroring.
23 // Disable the flipping and mirroring checks once we have it.
24 static bool CanFastPaint(SkCanvas
* canvas
, const gfx::Rect
& dest_rect
,
30 const SkMatrix
& total_matrix
= canvas
->getTotalMatrix();
31 // Perform the following checks here:
32 // 1. Check for skewing factors of the transformation matrix. They should be
34 // 2. Check for mirroring and flipping. Make sure they are greater than zero.
35 if (SkScalarNearlyZero(total_matrix
.getSkewX()) &&
36 SkScalarNearlyZero(total_matrix
.getSkewY()) &&
37 total_matrix
.getScaleX() > 0 &&
38 total_matrix
.getScaleY() > 0) {
39 SkDevice
* device
= canvas
->getDevice();
40 const SkBitmap::Config config
= device
->config();
42 if (config
== SkBitmap::kARGB_8888_Config
&& device
->isOpaque()) {
50 static bool IsEitherYV12OrYV16(media::VideoFrame::Format format
) {
51 return format
== media::VideoFrame::YV12
|| format
== media::VideoFrame::YV16
;
54 // Fast paint does YUV => RGB, scaling, blitting all in one step into the
55 // canvas. It's not always safe and appropriate to perform fast paint.
56 // CanFastPaint() is used to determine the conditions.
57 static void FastPaint(
58 const scoped_refptr
<media::VideoFrame
>& video_frame
,
60 const gfx::Rect
& dest_rect
) {
61 DCHECK(IsEitherYV12OrYV16(video_frame
->format())) << video_frame
->format();
62 DCHECK_EQ(video_frame
->stride(media::VideoFrame::kUPlane
),
63 video_frame
->stride(media::VideoFrame::kVPlane
));
65 const SkBitmap
& bitmap
= canvas
->getDevice()->accessBitmap(true);
66 media::YUVType yuv_type
= (video_frame
->format() == media::VideoFrame::YV12
) ?
67 media::YV12
: media::YV16
;
68 int y_shift
= yuv_type
; // 1 for YV12, 0 for YV16.
70 // Create a rectangle backed by SkScalar.
71 SkRect scalar_dest_rect
;
72 scalar_dest_rect
.iset(dest_rect
.x(), dest_rect
.y(),
73 dest_rect
.right(), dest_rect
.bottom());
75 // Transform the destination rectangle to local coordinates.
76 const SkMatrix
& local_matrix
= canvas
->getTotalMatrix();
77 SkRect local_dest_rect
;
78 local_matrix
.mapRect(&local_dest_rect
, scalar_dest_rect
);
80 // After projecting the destination rectangle to local coordinates, round
81 // the projected rectangle to integer values, this will give us pixel values
83 SkIRect local_dest_irect
, local_dest_irect_saved
;
84 local_dest_rect
.round(&local_dest_irect
);
85 local_dest_rect
.round(&local_dest_irect_saved
);
87 // No point painting if the destination rect doesn't intersect with the
89 if (!local_dest_irect
.intersect(canvas
->getTotalClip().getBounds()))
92 // At this point |local_dest_irect| contains the rect that we should draw
93 // to within the clipping rect.
95 // Calculate the address for the top left corner of destination rect in
96 // the canvas that we will draw to. The address is obtained by the base
97 // address of the canvas shifted by "left" and "top" of the rect.
98 uint8
* dest_rect_pointer
= static_cast<uint8
*>(bitmap
.getPixels()) +
99 local_dest_irect
.fTop
* bitmap
.rowBytes() +
100 local_dest_irect
.fLeft
* 4;
102 // Project the clip rect to the original video frame, obtains the
103 // dimensions of the projected clip rect, "left" and "top" of the rect.
104 // The math here are all integer math so we won't have rounding error and
105 // write outside of the canvas.
106 // We have the assumptions of dest_rect.width() and dest_rect.height()
107 // being non-zero, these are valid assumptions since finding intersection
108 // above rejects empty rectangle so we just do a DCHECK here.
109 DCHECK_NE(0, dest_rect
.width());
110 DCHECK_NE(0, dest_rect
.height());
111 size_t frame_clip_width
= local_dest_irect
.width() *
112 video_frame
->width() / local_dest_irect_saved
.width();
113 size_t frame_clip_height
= local_dest_irect
.height() *
114 video_frame
->height() / local_dest_irect_saved
.height();
116 // Project the "left" and "top" of the final destination rect to local
117 // coordinates of the video frame, use these values to find the offsets
118 // in the video frame to start reading.
119 size_t frame_clip_left
=
120 (local_dest_irect
.fLeft
- local_dest_irect_saved
.fLeft
) *
121 video_frame
->width() / local_dest_irect_saved
.width();
122 size_t frame_clip_top
=
123 (local_dest_irect
.fTop
- local_dest_irect_saved
.fTop
) *
124 video_frame
->height() / local_dest_irect_saved
.height();
126 // Use the "left" and "top" of the destination rect to locate the offset
127 // in Y, U and V planes.
128 size_t y_offset
= video_frame
->stride(media::VideoFrame::kYPlane
) *
129 frame_clip_top
+ frame_clip_left
;
131 // For format YV12, there is one U, V value per 2x2 block.
132 // For format YV16, there is one u, V value per 2x1 block.
133 size_t uv_offset
= (video_frame
->stride(media::VideoFrame::kUPlane
) *
134 (frame_clip_top
>> y_shift
)) + (frame_clip_left
>> 1);
135 uint8
* frame_clip_y
=
136 video_frame
->data(media::VideoFrame::kYPlane
) + y_offset
;
137 uint8
* frame_clip_u
=
138 video_frame
->data(media::VideoFrame::kUPlane
) + uv_offset
;
139 uint8
* frame_clip_v
=
140 video_frame
->data(media::VideoFrame::kVPlane
) + uv_offset
;
142 // TODO(hclam): do rotation and mirroring here.
143 // TODO(fbarchard): switch filtering based on performance.
145 media::ScaleYUVToRGB32(frame_clip_y
,
151 local_dest_irect
.width(),
152 local_dest_irect
.height(),
153 video_frame
->stride(media::VideoFrame::kYPlane
),
154 video_frame
->stride(media::VideoFrame::kUPlane
),
158 media::FILTER_BILINEAR
);
159 bitmap
.unlockPixels();
162 // Converts a VideoFrame containing YUV data to a SkBitmap containing RGB data.
164 // |bitmap| will be (re)allocated to match the dimensions of |video_frame|.
165 static void ConvertVideoFrameToBitmap(
166 const scoped_refptr
<media::VideoFrame
>& video_frame
,
168 DCHECK(IsEitherYV12OrYV16(video_frame
->format())) << video_frame
->format();
169 DCHECK(video_frame
->stride(media::VideoFrame::kUPlane
) ==
170 video_frame
->stride(media::VideoFrame::kVPlane
));
172 // Check if |bitmap| needs to be (re)allocated.
173 if (bitmap
->isNull() ||
174 bitmap
->width() != static_cast<int>(video_frame
->width()) ||
175 bitmap
->height() != static_cast<int>(video_frame
->height())) {
176 bitmap
->setConfig(SkBitmap::kARGB_8888_Config
,
177 video_frame
->width(),
178 video_frame
->height());
179 bitmap
->allocPixels();
180 bitmap
->setIsVolatile(true);
183 bitmap
->lockPixels();
184 media::YUVType yuv_type
=
185 (video_frame
->format() == media::VideoFrame::YV12
) ?
186 media::YV12
: media::YV16
;
187 media::ConvertYUVToRGB32(video_frame
->data(media::VideoFrame::kYPlane
),
188 video_frame
->data(media::VideoFrame::kUPlane
),
189 video_frame
->data(media::VideoFrame::kVPlane
),
190 static_cast<uint8
*>(bitmap
->getPixels()),
191 video_frame
->width(),
192 video_frame
->height(),
193 video_frame
->stride(media::VideoFrame::kYPlane
),
194 video_frame
->stride(media::VideoFrame::kUPlane
),
197 bitmap
->notifyPixelsChanged();
198 bitmap
->unlockPixels();
201 SkCanvasVideoRenderer::SkCanvasVideoRenderer()
202 : last_frame_timestamp_(media::kNoTimestamp()) {
205 SkCanvasVideoRenderer::~SkCanvasVideoRenderer() {}
207 void SkCanvasVideoRenderer::Paint(media::VideoFrame
* video_frame
,
209 const gfx::Rect
& dest_rect
,
216 dest
.set(SkIntToScalar(dest_rect
.x()), SkIntToScalar(dest_rect
.y()),
217 SkIntToScalar(dest_rect
.right()), SkIntToScalar(dest_rect
.bottom()));
220 paint
.setAlpha(alpha
);
222 // Paint black rectangle if there isn't a frame available or if the format is
223 // unexpected (can happen e.g. when normally painting to HW textures but
224 // during shutdown path).
225 if (!video_frame
|| !IsEitherYV12OrYV16(video_frame
->format())) {
226 canvas
->drawRect(dest
, paint
);
230 // Scale and convert to RGB in one step if we can.
231 if (CanFastPaint(canvas
, dest_rect
, alpha
)) {
232 FastPaint(video_frame
, canvas
, dest_rect
);
236 // Check if we should convert and update |last_frame_|.
237 if (last_frame_
.isNull() ||
238 video_frame
->GetTimestamp() != last_frame_timestamp_
) {
239 ConvertVideoFrameToBitmap(video_frame
, &last_frame_
);
240 last_frame_timestamp_
= video_frame
->GetTimestamp();
243 // Do a slower paint using |last_frame_|.
244 paint
.setFilterBitmap(true);
245 canvas
->drawBitmapRect(last_frame_
, NULL
, dest
, &paint
);
248 } // namespace webkit_media