OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "webkit/media/skcanvas_video_renderer.h" | |
6 | |
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" | |
12 | |
13 namespace webkit_media { | |
14 | |
15 static bool IsEitherYV12OrYV16(media::VideoFrame::Format format) { | |
16 return format == media::VideoFrame::YV12 || format == media::VideoFrame::YV16; | |
17 } | |
18 | |
19 static bool IsEitherYV12OrYV16OrNative(media::VideoFrame::Format format) { | |
20 return IsEitherYV12OrYV16(format) || | |
21 format == media::VideoFrame::NATIVE_TEXTURE; | |
22 } | |
23 | |
24 // CanFastPaint is a helper method to determine the conditions for fast | |
25 // painting. The conditions are: | |
26 // 1. No skew in canvas matrix. | |
27 // 2. No flipping nor mirroring. | |
28 // 3. Canvas has pixel format ARGB8888. | |
29 // 4. Canvas is opaque. | |
30 // 5. Frame format is YV12 or YV16. | |
31 // | |
32 // TODO(hclam): The fast paint method should support flipping and mirroring. | |
33 // Disable the flipping and mirroring checks once we have it. | |
34 static bool CanFastPaint(SkCanvas* canvas, const gfx::Rect& dest_rect, | |
35 uint8_t alpha, media::VideoFrame::Format format) { | |
36 if (alpha != 0xFF || !IsEitherYV12OrYV16(format)) | |
37 return false; | |
38 | |
39 const SkMatrix& total_matrix = canvas->getTotalMatrix(); | |
40 // Perform the following checks here: | |
41 // 1. Check for skewing factors of the transformation matrix. They should be | |
42 // zero. | |
43 // 2. Check for mirroring and flipping. Make sure they are greater than zero. | |
44 if (SkScalarNearlyZero(total_matrix.getSkewX()) && | |
45 SkScalarNearlyZero(total_matrix.getSkewY()) && | |
46 total_matrix.getScaleX() > 0 && | |
47 total_matrix.getScaleY() > 0) { | |
48 SkDevice* device = canvas->getDevice(); | |
49 const SkBitmap::Config config = device->config(); | |
50 | |
51 if (config == SkBitmap::kARGB_8888_Config && device->isOpaque()) { | |
52 return true; | |
53 } | |
54 } | |
55 | |
56 return false; | |
57 } | |
58 | |
59 // Fast paint does YUV => RGB, scaling, blitting all in one step into the | |
60 // canvas. It's not always safe and appropriate to perform fast paint. | |
61 // CanFastPaint() is used to determine the conditions. | |
62 static void FastPaint( | |
63 const scoped_refptr<media::VideoFrame>& video_frame, | |
64 SkCanvas* canvas, | |
65 const gfx::Rect& dest_rect) { | |
66 DCHECK(IsEitherYV12OrYV16(video_frame->format())) << video_frame->format(); | |
67 DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane), | |
68 video_frame->stride(media::VideoFrame::kVPlane)); | |
69 | |
70 const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(true); | |
71 media::YUVType yuv_type = (video_frame->format() == media::VideoFrame::YV12) ? | |
72 media::YV12 : media::YV16; | |
73 int y_shift = yuv_type; // 1 for YV12, 0 for YV16. | |
74 | |
75 // Create a rectangle backed by SkScalar. | |
76 SkRect scalar_dest_rect; | |
77 scalar_dest_rect.iset(dest_rect.x(), dest_rect.y(), | |
78 dest_rect.right(), dest_rect.bottom()); | |
79 | |
80 // Transform the destination rectangle to local coordinates. | |
81 const SkMatrix& local_matrix = canvas->getTotalMatrix(); | |
82 SkRect local_dest_rect; | |
83 local_matrix.mapRect(&local_dest_rect, scalar_dest_rect); | |
84 | |
85 // After projecting the destination rectangle to local coordinates, round | |
86 // the projected rectangle to integer values, this will give us pixel values | |
87 // of the rectangle. | |
88 SkIRect local_dest_irect, local_dest_irect_saved; | |
89 local_dest_rect.round(&local_dest_irect); | |
90 local_dest_rect.round(&local_dest_irect_saved); | |
91 | |
92 // No point painting if the destination rect doesn't intersect with the | |
93 // clip rect. | |
94 if (!local_dest_irect.intersect(canvas->getTotalClip().getBounds())) | |
95 return; | |
96 | |
97 // At this point |local_dest_irect| contains the rect that we should draw | |
98 // to within the clipping rect. | |
99 | |
100 // Calculate the address for the top left corner of destination rect in | |
101 // the canvas that we will draw to. The address is obtained by the base | |
102 // address of the canvas shifted by "left" and "top" of the rect. | |
103 uint8* dest_rect_pointer = static_cast<uint8*>(bitmap.getPixels()) + | |
104 local_dest_irect.fTop * bitmap.rowBytes() + | |
105 local_dest_irect.fLeft * 4; | |
106 | |
107 // Project the clip rect to the original video frame, obtains the | |
108 // dimensions of the projected clip rect, "left" and "top" of the rect. | |
109 // The math here are all integer math so we won't have rounding error and | |
110 // write outside of the canvas. | |
111 // We have the assumptions of dest_rect.width() and dest_rect.height() | |
112 // being non-zero, these are valid assumptions since finding intersection | |
113 // above rejects empty rectangle so we just do a DCHECK here. | |
114 DCHECK_NE(0, dest_rect.width()); | |
115 DCHECK_NE(0, dest_rect.height()); | |
116 size_t frame_clip_width = local_dest_irect.width() * | |
117 video_frame->data_size().width() / local_dest_irect_saved.width(); | |
118 size_t frame_clip_height = local_dest_irect.height() * | |
119 video_frame->data_size().height() / local_dest_irect_saved.height(); | |
120 | |
121 // Project the "left" and "top" of the final destination rect to local | |
122 // coordinates of the video frame, use these values to find the offsets | |
123 // in the video frame to start reading. | |
124 size_t frame_clip_left = | |
125 (local_dest_irect.fLeft - local_dest_irect_saved.fLeft) * | |
126 video_frame->data_size().width() / local_dest_irect_saved.width(); | |
127 size_t frame_clip_top = | |
128 (local_dest_irect.fTop - local_dest_irect_saved.fTop) * | |
129 video_frame->data_size().height() / local_dest_irect_saved.height(); | |
130 | |
131 // Use the "left" and "top" of the destination rect to locate the offset | |
132 // in Y, U and V planes. | |
133 size_t y_offset = video_frame->stride(media::VideoFrame::kYPlane) * | |
134 frame_clip_top + frame_clip_left; | |
135 | |
136 // For format YV12, there is one U, V value per 2x2 block. | |
137 // For format YV16, there is one u, V value per 2x1 block. | |
138 size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) * | |
139 (frame_clip_top >> y_shift)) + (frame_clip_left >> 1); | |
140 uint8* frame_clip_y = | |
141 video_frame->data(media::VideoFrame::kYPlane) + y_offset; | |
142 uint8* frame_clip_u = | |
143 video_frame->data(media::VideoFrame::kUPlane) + uv_offset; | |
144 uint8* frame_clip_v = | |
145 video_frame->data(media::VideoFrame::kVPlane) + uv_offset; | |
146 | |
147 // TODO(hclam): do rotation and mirroring here. | |
148 // TODO(fbarchard): switch filtering based on performance. | |
149 bitmap.lockPixels(); | |
150 media::ScaleYUVToRGB32(frame_clip_y, | |
151 frame_clip_u, | |
152 frame_clip_v, | |
153 dest_rect_pointer, | |
154 frame_clip_width, | |
155 frame_clip_height, | |
156 local_dest_irect.width(), | |
157 local_dest_irect.height(), | |
158 video_frame->stride(media::VideoFrame::kYPlane), | |
159 video_frame->stride(media::VideoFrame::kUPlane), | |
160 bitmap.rowBytes(), | |
161 yuv_type, | |
162 media::ROTATE_0, | |
163 media::FILTER_BILINEAR); | |
164 bitmap.unlockPixels(); | |
165 } | |
166 | |
167 // Converts a VideoFrame containing YUV data to a SkBitmap containing RGB data. | |
168 // | |
169 // |bitmap| will be (re)allocated to match the dimensions of |video_frame|. | |
170 static void ConvertVideoFrameToBitmap( | |
171 const scoped_refptr<media::VideoFrame>& video_frame, | |
172 SkBitmap* bitmap) { | |
173 DCHECK(IsEitherYV12OrYV16OrNative(video_frame->format())) | |
174 << video_frame->format(); | |
175 if (IsEitherYV12OrYV16(video_frame->format())) { | |
176 DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane), | |
177 video_frame->stride(media::VideoFrame::kVPlane)); | |
178 } | |
179 | |
180 // Check if |bitmap| needs to be (re)allocated. | |
181 if (bitmap->isNull() || | |
182 bitmap->width() != video_frame->data_size().width() || | |
183 bitmap->height() != video_frame->data_size().height()) { | |
184 bitmap->setConfig(SkBitmap::kARGB_8888_Config, | |
185 video_frame->data_size().width(), | |
186 video_frame->data_size().height()); | |
187 bitmap->allocPixels(); | |
188 bitmap->setIsVolatile(true); | |
189 } | |
190 | |
191 bitmap->lockPixels(); | |
192 if (IsEitherYV12OrYV16(video_frame->format())) { | |
193 media::YUVType yuv_type = | |
194 (video_frame->format() == media::VideoFrame::YV12) ? | |
195 media::YV12 : media::YV16; | |
196 media::ConvertYUVToRGB32(video_frame->data(media::VideoFrame::kYPlane), | |
197 video_frame->data(media::VideoFrame::kUPlane), | |
198 video_frame->data(media::VideoFrame::kVPlane), | |
199 static_cast<uint8*>(bitmap->getPixels()), | |
200 video_frame->data_size().width(), | |
201 video_frame->data_size().height(), | |
202 video_frame->stride(media::VideoFrame::kYPlane), | |
203 video_frame->stride(media::VideoFrame::kUPlane), | |
204 bitmap->rowBytes(), | |
205 yuv_type); | |
206 } else { | |
207 DCHECK_EQ(video_frame->format(), media::VideoFrame::NATIVE_TEXTURE); | |
208 video_frame->ReadPixelsFromNativeTexture(bitmap->getPixels()); | |
209 } | |
210 bitmap->notifyPixelsChanged(); | |
211 bitmap->unlockPixels(); | |
212 } | |
213 | |
214 SkCanvasVideoRenderer::SkCanvasVideoRenderer() | |
215 : last_frame_timestamp_(media::kNoTimestamp()) { | |
216 } | |
217 | |
218 SkCanvasVideoRenderer::~SkCanvasVideoRenderer() {} | |
219 | |
220 void SkCanvasVideoRenderer::Paint(media::VideoFrame* video_frame, | |
221 SkCanvas* canvas, | |
222 const gfx::Rect& dest_rect, | |
223 uint8_t alpha) { | |
224 if (alpha == 0) { | |
225 return; | |
226 } | |
227 | |
228 SkRect dest; | |
229 dest.set(SkIntToScalar(dest_rect.x()), SkIntToScalar(dest_rect.y()), | |
230 SkIntToScalar(dest_rect.right()), SkIntToScalar(dest_rect.bottom())); | |
231 | |
232 SkPaint paint; | |
233 paint.setAlpha(alpha); | |
234 | |
235 // Paint black rectangle if there isn't a frame available or the | |
236 // frame has an unexpected format. | |
237 if (!video_frame || !IsEitherYV12OrYV16OrNative(video_frame->format())) { | |
238 canvas->drawRect(dest, paint); | |
239 return; | |
240 } | |
241 | |
242 // Scale and convert to RGB in one step if we can. | |
243 if (CanFastPaint(canvas, dest_rect, alpha, video_frame->format())) { | |
244 FastPaint(video_frame, canvas, dest_rect); | |
245 return; | |
246 } | |
247 | |
248 // Check if we should convert and update |last_frame_|. | |
249 if (last_frame_.isNull() || | |
250 video_frame->GetTimestamp() != last_frame_timestamp_) { | |
251 ConvertVideoFrameToBitmap(video_frame, &last_frame_); | |
252 last_frame_timestamp_ = video_frame->GetTimestamp(); | |
253 } | |
254 | |
255 // Do a slower paint using |last_frame_|. | |
256 paint.setFilterBitmap(true); | |
257 canvas->drawBitmapRect(last_frame_, NULL, dest, &paint); | |
258 } | |
259 | |
260 } // namespace webkit_media | |
OLD | NEW |