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 "base/bind.h" | |
6 #include "base/lazy_instance.h" | |
7 #include "base/message_loop.h" | |
8 #include "chrome/browser/speech/speech_input_bubble.h" | |
9 #include "content/public/browser/web_contents.h" | |
10 #include "grit/generated_resources.h" | |
11 #include "grit/theme_resources.h" | |
12 #include "ui/base/resource/resource_bundle.h" | |
13 #include "ui/gfx/canvas_skia.h" | |
14 #include "ui/gfx/rect.h" | |
15 #include "ui/gfx/skbitmap_operations.h" | |
16 | |
17 using content::WebContents; | |
18 | |
19 namespace { | |
20 | |
21 const color_utils::HSL kGrayscaleShift = { -1, 0, 0.6 }; | |
22 const int kWarmingUpAnimationStartMs = 500; | |
23 const int kWarmingUpAnimationStepMs = 100; | |
24 const int kRecognizingAnimationStepMs = 100; | |
25 | |
26 // A lazily initialized singleton to hold all the image used by the speech | |
27 // input bubbles and safely destroy them on exit. | |
28 class SpeechInputBubbleImages { | |
29 public: | |
30 const std::vector<SkBitmap>& spinner() { return spinner_; } | |
31 const std::vector<SkBitmap>& warm_up() { return warm_up_; } | |
32 SkBitmap* mic_full() { return mic_full_; } | |
33 SkBitmap* mic_empty() { return mic_empty_; } | |
34 SkBitmap* mic_noise() { return mic_noise_; } | |
35 SkBitmap* mic_mask() { return mic_mask_; } | |
36 | |
37 private: | |
38 // Private constructor to enforce singleton. | |
39 friend struct base::DefaultLazyInstanceTraits<SpeechInputBubbleImages>; | |
40 SpeechInputBubbleImages(); | |
41 | |
42 std::vector<SkBitmap> spinner_; // Frames for the progress spinner. | |
43 std::vector<SkBitmap> warm_up_; // Frames for the warm up animation. | |
44 | |
45 // These bitmaps are owned by ResourceBundle and need not be destroyed. | |
46 SkBitmap* mic_full_; // Mic image with full volume. | |
47 SkBitmap* mic_noise_; // Mic image with full noise volume. | |
48 SkBitmap* mic_empty_; // Mic image with zero volume. | |
49 SkBitmap* mic_mask_; // Gradient mask used by the volume indicator. | |
50 }; | |
51 | |
52 SpeechInputBubbleImages::SpeechInputBubbleImages() { | |
53 mic_empty_ = ResourceBundle::GetSharedInstance().GetBitmapNamed( | |
54 IDR_SPEECH_INPUT_MIC_EMPTY); | |
55 mic_noise_ = ResourceBundle::GetSharedInstance().GetBitmapNamed( | |
56 IDR_SPEECH_INPUT_MIC_NOISE); | |
57 mic_full_ = ResourceBundle::GetSharedInstance().GetBitmapNamed( | |
58 IDR_SPEECH_INPUT_MIC_FULL); | |
59 mic_mask_ = ResourceBundle::GetSharedInstance().GetBitmapNamed( | |
60 IDR_SPEECH_INPUT_MIC_MASK); | |
61 | |
62 // The sprite image consists of all the animation frames put together in one | |
63 // horizontal/wide image. Each animation frame is square in shape within the | |
64 // sprite. | |
65 SkBitmap* spinner_image = ResourceBundle::GetSharedInstance().GetBitmapNamed( | |
66 IDR_SPEECH_INPUT_SPINNER); | |
67 int frame_size = spinner_image->height(); | |
68 | |
69 // When recording starts up, it may take a short while (few ms or even a | |
70 // couple of seconds) before the audio device starts really capturing data. | |
71 // This is more apparent on first use. To cover such cases we show a warming | |
72 // up state in the bubble starting with a blank spinner image. If audio data | |
73 // starts coming in within a couple hundred ms, we switch to the recording | |
74 // UI and if it takes longer, we show the real warm up animation frames. | |
75 // This reduces visual jank for the most part. | |
76 SkBitmap empty_spinner; | |
77 empty_spinner.setConfig(SkBitmap::kARGB_8888_Config, frame_size, frame_size); | |
78 empty_spinner.allocPixels(); | |
79 empty_spinner.eraseRGB(255, 255, 255); | |
80 warm_up_.push_back(empty_spinner); | |
81 | |
82 for (SkIRect src_rect(SkIRect::MakeWH(frame_size, frame_size)); | |
83 src_rect.fLeft < spinner_image->width(); | |
84 src_rect.offset(frame_size, 0)) { | |
85 SkBitmap frame; | |
86 spinner_image->extractSubset(&frame, src_rect); | |
87 | |
88 // The bitmap created by extractSubset just points to the same pixels as | |
89 // the original and adjusts rowBytes accordingly. However that doesn't | |
90 // render properly and gets vertically squished in Linux due to a bug in | |
91 // Skia. Until that gets fixed we work around by taking a real copy of it | |
92 // below as the copied bitmap has the correct rowBytes and renders fine. | |
93 SkBitmap frame_copy; | |
94 frame.copyTo(&frame_copy, SkBitmap::kARGB_8888_Config); | |
95 spinner_.push_back(frame_copy); | |
96 | |
97 // The warm up spinner animation is a gray scale version of the real one. | |
98 warm_up_.push_back(SkBitmapOperations::CreateHSLShiftedBitmap( | |
99 frame_copy, kGrayscaleShift)); | |
100 } | |
101 } | |
102 | |
103 base::LazyInstance<SpeechInputBubbleImages> g_images = | |
104 LAZY_INSTANCE_INITIALIZER; | |
105 | |
106 } // namespace | |
107 | |
108 SpeechInputBubble::FactoryMethod SpeechInputBubble::factory_ = NULL; | |
109 const int SpeechInputBubble::kBubbleTargetOffsetX = 10; | |
110 | |
111 SpeechInputBubble* SpeechInputBubble::Create(WebContents* web_contents, | |
112 Delegate* delegate, | |
113 const gfx::Rect& element_rect) { | |
114 if (factory_) | |
115 return (*factory_)(web_contents, delegate, element_rect); | |
116 | |
117 // Has the tab already closed before bubble create request was processed? | |
118 if (!web_contents) | |
119 return NULL; | |
120 | |
121 return CreateNativeBubble(web_contents, delegate, element_rect); | |
122 } | |
123 | |
124 SpeechInputBubbleBase::SpeechInputBubbleBase(WebContents* web_contents) | |
125 : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), | |
126 display_mode_(DISPLAY_MODE_RECORDING), | |
127 web_contents_(web_contents) { | |
128 mic_image_.reset(new SkBitmap()); | |
129 mic_image_->setConfig(SkBitmap::kARGB_8888_Config, | |
130 g_images.Get().mic_empty()->width(), | |
131 g_images.Get().mic_empty()->height()); | |
132 mic_image_->allocPixels(); | |
133 | |
134 buffer_image_.reset(new SkBitmap()); | |
135 buffer_image_->setConfig(SkBitmap::kARGB_8888_Config, | |
136 g_images.Get().mic_empty()->width(), | |
137 g_images.Get().mic_empty()->height()); | |
138 buffer_image_->allocPixels(); | |
139 } | |
140 | |
141 SpeechInputBubbleBase::~SpeechInputBubbleBase() { | |
142 // This destructor is added to make sure members such as the scoped_ptr | |
143 // get destroyed here and the derived classes don't have to care about such | |
144 // member variables which they don't use. | |
145 } | |
146 | |
147 void SpeechInputBubbleBase::SetWarmUpMode() { | |
148 weak_factory_.InvalidateWeakPtrs(); | |
149 display_mode_ = DISPLAY_MODE_WARM_UP; | |
150 animation_step_ = 0; | |
151 DoWarmingUpAnimationStep(); | |
152 UpdateLayout(); | |
153 } | |
154 | |
155 void SpeechInputBubbleBase::DoWarmingUpAnimationStep() { | |
156 SetImage(g_images.Get().warm_up()[animation_step_]); | |
157 MessageLoop::current()->PostDelayedTask( | |
158 FROM_HERE, | |
159 base::Bind(&SpeechInputBubbleBase::DoWarmingUpAnimationStep, | |
160 weak_factory_.GetWeakPtr()), | |
161 base::TimeDelta::FromMilliseconds( | |
162 animation_step_ == 0 ? kWarmingUpAnimationStartMs | |
163 : kWarmingUpAnimationStepMs)); | |
164 if (++animation_step_ >= static_cast<int>(g_images.Get().warm_up().size())) | |
165 animation_step_ = 1; // Frame 0 is skipped during the animation. | |
166 } | |
167 | |
168 void SpeechInputBubbleBase::SetRecordingMode() { | |
169 weak_factory_.InvalidateWeakPtrs(); | |
170 display_mode_ = DISPLAY_MODE_RECORDING; | |
171 SetInputVolume(0, 0); | |
172 UpdateLayout(); | |
173 } | |
174 | |
175 void SpeechInputBubbleBase::SetRecognizingMode() { | |
176 display_mode_ = DISPLAY_MODE_RECOGNIZING; | |
177 animation_step_ = 0; | |
178 DoRecognizingAnimationStep(); | |
179 UpdateLayout(); | |
180 } | |
181 | |
182 void SpeechInputBubbleBase::DoRecognizingAnimationStep() { | |
183 SetImage(g_images.Get().spinner()[animation_step_]); | |
184 if (++animation_step_ >= static_cast<int>(g_images.Get().spinner().size())) | |
185 animation_step_ = 0; | |
186 MessageLoop::current()->PostDelayedTask( | |
187 FROM_HERE, | |
188 base::Bind(&SpeechInputBubbleBase::DoRecognizingAnimationStep, | |
189 weak_factory_.GetWeakPtr()), | |
190 base::TimeDelta::FromMilliseconds(kRecognizingAnimationStepMs)); | |
191 } | |
192 | |
193 void SpeechInputBubbleBase::SetMessage(const string16& text) { | |
194 weak_factory_.InvalidateWeakPtrs(); | |
195 message_text_ = text; | |
196 display_mode_ = DISPLAY_MODE_MESSAGE; | |
197 UpdateLayout(); | |
198 } | |
199 | |
200 void SpeechInputBubbleBase::DrawVolumeOverlay(SkCanvas* canvas, | |
201 const SkBitmap& bitmap, | |
202 float volume) { | |
203 buffer_image_->eraseARGB(0, 0, 0, 0); | |
204 | |
205 int width = mic_image_->width(); | |
206 int height = mic_image_->height(); | |
207 SkCanvas buffer_canvas(*buffer_image_); | |
208 | |
209 buffer_canvas.save(); | |
210 const int kVolumeSteps = 12; | |
211 SkScalar clip_right = | |
212 (((1.0f - volume) * (width * (kVolumeSteps + 1))) - width) / kVolumeSteps; | |
213 buffer_canvas.clipRect(SkRect::MakeLTRB(0, 0, | |
214 SkIntToScalar(width) - clip_right, SkIntToScalar(height))); | |
215 buffer_canvas.drawBitmap(bitmap, 0, 0); | |
216 buffer_canvas.restore(); | |
217 SkPaint multiply_paint; | |
218 multiply_paint.setXfermode(SkXfermode::Create(SkXfermode::kMultiply_Mode)); | |
219 buffer_canvas.drawBitmap(*g_images.Get().mic_mask(), -clip_right, 0, | |
220 &multiply_paint); | |
221 | |
222 canvas->drawBitmap(*buffer_image_.get(), 0, 0); | |
223 } | |
224 | |
225 void SpeechInputBubbleBase::SetInputVolume(float volume, float noise_volume) { | |
226 mic_image_->eraseARGB(0, 0, 0, 0); | |
227 SkCanvas canvas(*mic_image_); | |
228 | |
229 // Draw the empty volume image first and the current volume image on top, | |
230 // and then the noise volume image on top of both. | |
231 canvas.drawBitmap(*g_images.Get().mic_empty(), 0, 0); | |
232 DrawVolumeOverlay(&canvas, *g_images.Get().mic_full(), volume); | |
233 DrawVolumeOverlay(&canvas, *g_images.Get().mic_noise(), noise_volume); | |
234 | |
235 SetImage(*mic_image_.get()); | |
236 } | |
237 | |
238 WebContents* SpeechInputBubbleBase::web_contents() { | |
239 return web_contents_; | |
240 } | |
241 | |
242 void SpeechInputBubbleBase::SetImage(const SkBitmap& image) { | |
243 icon_image_.reset(new SkBitmap(image)); | |
244 UpdateImage(); | |
245 } | |
246 | |
247 SkBitmap SpeechInputBubbleBase::icon_image() { | |
248 return (icon_image_ != NULL) ? *icon_image_ : SkBitmap(); | |
249 } | |
OLD | NEW |