Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 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 "content/browser/android/edge_effect_l.h" | |
| 6 | |
| 7 #include "base/debug/trace_event.h" | |
| 8 #include "base/lazy_instance.h" | |
| 9 #include "cc/layers/image_layer.h" | |
| 10 #include "third_party/skia/include/core/SkBitmap.h" | |
| 11 #include "third_party/skia/include/core/SkCanvas.h" | |
| 12 #include "third_party/skia/include/core/SkPaint.h" | |
| 13 #include "third_party/skia/include/effects/SkPorterDuff.h" | |
| 14 #include "ui/gfx/screen.h" | |
| 15 | |
| 16 namespace content { | |
| 17 | |
| 18 namespace { | |
| 19 | |
| 20 // Time it will take the effect to fully recede in ms | |
| 21 const int kRecedeTime = 1000; | |
| 22 | |
| 23 // Time it will take before a pulled glow begins receding in ms | |
| 24 const int kPullTime = 167; | |
| 25 | |
| 26 const float kMaxAlpha = 1.f; | |
| 27 | |
| 28 const float kPullGlowBegin = 0.f; | |
| 29 | |
| 30 // Min/max velocity that will be absorbed | |
| 31 const float kMinVelocity = 100.f; | |
| 32 const float kMaxVelocity = 10000.f; | |
| 33 | |
| 34 const float kEpsilon = 0.001f; | |
| 35 | |
| 36 const float kSin = 0.5f; // sin(PI / 6) | |
| 37 const float kCos = 0.866f; // cos(PI / 6); | |
| 38 | |
| 39 // How much dragging should effect the height of the glow image. | |
| 40 // Number determined by user testing. | |
| 41 const float kPullDistanceAlphaGlowFactor = 1.1f; | |
| 42 | |
| 43 const int kVelocityGlowFactor = 12; | |
| 44 | |
| 45 template <typename T> | |
| 46 T Lerp(T a, T b, T t) { | |
| 47 return a + (b - a) * t; | |
| 48 } | |
| 49 | |
| 50 template <typename T> | |
| 51 T Clamp(T value, T low, T high) { | |
| 52 return value < low ? low : (value > high ? high : value); | |
| 53 } | |
| 54 | |
| 55 template <typename T> | |
| 56 T Damp(T input, T factor) { | |
| 57 T result; | |
| 58 if (factor == 1) { | |
| 59 result = 1 - (1 - input) * (1 - input); | |
| 60 } else { | |
| 61 result = 1 - std::pow(1 - input, 2 * factor); | |
| 62 } | |
| 63 return result; | |
| 64 } | |
| 65 | |
| 66 void DisableLayer(cc::Layer* layer) { | |
| 67 DCHECK(layer); | |
| 68 layer->SetIsDrawable(false); | |
| 69 layer->SetTransform(gfx::Transform()); | |
| 70 layer->SetOpacity(1.f); | |
| 71 } | |
| 72 | |
| 73 scoped_refptr<cc::Layer> CreateImageLayer(const SkBitmap& bitmap) { | |
| 74 scoped_refptr<cc::ImageLayer> layer = cc::ImageLayer::Create(); | |
| 75 layer->SetBitmap(bitmap); | |
| 76 return layer; | |
| 77 } | |
| 78 | |
| 79 class EdgeEffectLResources { | |
| 80 public: | |
| 81 EdgeEffectLResources() { | |
| 82 TRACE_EVENT0("browser", "EdgeEffectLResources::Create"); | |
| 83 // TODO(jdduke): Fetch the theme color from the platform as follows: | |
| 84 /*final TypedArray a = context.obtainStyledAttributes( | |
| 85 com.android.internal.R.styleable.EdgeEffect); | |
| 86 final int themeColor = a.getColor( | |
| 87 com.android.internal.R.styleable.EdgeEffect_colorPrimary, 0xff666666);*/ | |
| 88 const int kThemeColor = 0xff666666; | |
| 89 | |
| 90 SkPaint paint; | |
| 91 paint.setAntiAlias(true); | |
| 92 paint.setColor((kThemeColor & 0xffffff) | 0x33000000); | |
| 93 paint.setStyle(SkPaint::kFill_Style); | |
| 94 | |
| 95 gfx::Size screen_bounds = | |
| 96 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().GetSizeInPixel(); | |
| 97 const float width = std::max(screen_bounds.width(), screen_bounds.height()); | |
| 98 const float radius = width * 0.75f / kSin; | |
|
aelias_OOO_until_Jul13
2014/07/17 20:23:17
Looks like this ends up multiplying by 1.5, for ov
jdduke (slow)
2014/07/17 22:24:12
Hmm, the (raw sized) effect should be larger than
jdduke (slow)
2014/08/11 19:51:10
1.5 is right, but the variable name |radius| is mi
| |
| 99 const float y = kCos * radius; | |
| 100 const float height = radius - y; | |
| 101 gfx::Size bounds(radius, height); | |
| 102 SkRect arc_rect = SkRect::MakeXYWH( | |
| 103 -radius / 2.f, -radius - y, radius * 2.f, radius * 2.f); | |
| 104 if (!glow_bitmap_.allocPixels( | |
| 105 SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()))) { | |
| 106 LOG(FATAL) << " Failed to allocate bitmap of size " << bounds.width() | |
| 107 << "x" << bounds.height(); | |
| 108 } | |
| 109 | |
| 110 SkCanvas canvas(glow_bitmap_); | |
| 111 canvas.clipRect(SkRect::MakeXYWH(0, 0, bounds.width(), bounds.height())); | |
| 112 canvas.drawArc(arc_rect, 45, 90, true, paint); | |
| 113 } | |
| 114 | |
| 115 const SkBitmap& glow_bitmap() const { return glow_bitmap_; } | |
| 116 | |
| 117 private: | |
| 118 SkBitmap glow_bitmap_; | |
| 119 | |
| 120 DISALLOW_COPY_AND_ASSIGN(EdgeEffectLResources); | |
| 121 }; | |
| 122 | |
| 123 // Leaky to allow access from a worker thread. | |
| 124 base::LazyInstance<EdgeEffectLResources>::Leaky g_edge_effect_resources = | |
| 125 LAZY_INSTANCE_INITIALIZER; | |
| 126 | |
| 127 } // namespace | |
| 128 | |
| 129 // static | |
| 130 scoped_ptr<EdgeEffectL> EdgeEffectL::Create(cc::Layer* root_layer) { | |
| 131 DCHECK(root_layer); | |
| 132 | |
| 133 const SkBitmap& glow = g_edge_effect_resources.Get().glow_bitmap(); | |
| 134 if (glow.isNull()) | |
| 135 return scoped_ptr<EdgeEffectL>(); | |
| 136 | |
| 137 scoped_refptr<cc::Layer> glow_layer = CreateImageLayer(glow); | |
| 138 root_layer->AddChild(glow_layer); | |
| 139 return make_scoped_ptr(new EdgeEffectL(glow_layer)); | |
| 140 } | |
| 141 | |
| 142 EdgeEffectL::EdgeEffectL(const scoped_refptr<cc::Layer>& glow) | |
| 143 : glow_(glow) | |
| 144 , glow_alpha_(0) | |
| 145 , glow_scale_y_(0) | |
| 146 , glow_alpha_start_(0) | |
| 147 , glow_alpha_finish_(0) | |
| 148 , glow_scale_y_start_(0) | |
| 149 , glow_scale_y_finish_(0) | |
| 150 , displacement_(0.5f) | |
| 151 , target_displacement_(0.5f) | |
| 152 , state_(STATE_IDLE) | |
| 153 , pull_distance_(0) { | |
| 154 // Prevent the provided layers from drawing until the effect is activated. | |
| 155 DisableLayer(glow_.get()); | |
| 156 glow_->SetBlendMode(SkXfermode::kSrcATop_Mode); | |
| 157 } | |
| 158 | |
| 159 EdgeEffectL::~EdgeEffectL() { } | |
| 160 | |
| 161 bool EdgeEffectL::IsFinished() const { | |
| 162 return state_ == STATE_IDLE; | |
| 163 } | |
| 164 | |
| 165 void EdgeEffectL::Finish() { | |
| 166 DisableLayer(glow_.get()); | |
| 167 pull_distance_ = 0; | |
| 168 state_ = STATE_IDLE; | |
| 169 } | |
| 170 | |
| 171 void EdgeEffectL::Pull(base::TimeTicks current_time, float delta_distance) { | |
| 172 Pull(current_time, delta_distance, 0.5f); | |
| 173 } | |
| 174 | |
| 175 void EdgeEffectL::Pull(base::TimeTicks current_time, | |
| 176 float delta_distance, | |
| 177 float displacement) { | |
| 178 target_displacement_ = displacement; | |
| 179 if (state_ == STATE_PULL_DECAY && current_time - start_time_ < duration_) { | |
| 180 return; | |
| 181 } | |
| 182 if (state_ != STATE_PULL) { | |
| 183 glow_scale_y_ = std::max(kPullGlowBegin, glow_scale_y_); | |
| 184 } | |
| 185 state_ = STATE_PULL; | |
| 186 | |
| 187 start_time_ = current_time; | |
| 188 duration_ = base::TimeDelta::FromMilliseconds(kPullTime); | |
| 189 | |
| 190 float abs_delta_distance = std::abs(delta_distance); | |
| 191 pull_distance_ += delta_distance; | |
| 192 | |
| 193 glow_alpha_ = glow_alpha_start_ = std::min( | |
| 194 kMaxAlpha, | |
| 195 glow_alpha_ + (abs_delta_distance * kPullDistanceAlphaGlowFactor)); | |
| 196 | |
| 197 if (pull_distance_ == 0) { | |
| 198 glow_scale_y_ = glow_scale_y_start_ = 0; | |
| 199 } else { | |
| 200 float scale = 1.f - | |
| 201 1.f / std::sqrt(std::abs(pull_distance_) * bounds_.height()) - | |
| 202 0.3f; | |
| 203 glow_scale_y_ = glow_scale_y_start_ = std::max(0.f, scale) / 0.7f; | |
| 204 } | |
| 205 | |
| 206 glow_alpha_finish_ = glow_alpha_; | |
| 207 glow_scale_y_finish_ = glow_scale_y_; | |
| 208 } | |
| 209 | |
| 210 void EdgeEffectL::Release(base::TimeTicks current_time) { | |
| 211 pull_distance_ = 0; | |
| 212 | |
| 213 if (state_ != STATE_PULL && state_ != STATE_PULL_DECAY) | |
| 214 return; | |
| 215 | |
| 216 state_ = STATE_RECEDE; | |
| 217 glow_alpha_start_ = glow_alpha_; | |
| 218 glow_scale_y_start_ = glow_scale_y_; | |
| 219 | |
| 220 glow_alpha_finish_ = 0.f; | |
| 221 glow_scale_y_finish_ = 0.f; | |
| 222 | |
| 223 start_time_ = current_time; | |
| 224 duration_ = base::TimeDelta::FromMilliseconds(kRecedeTime); | |
| 225 } | |
| 226 | |
| 227 void EdgeEffectL::Absorb(base::TimeTicks current_time, float velocity) { | |
| 228 state_ = STATE_ABSORB; | |
| 229 | |
| 230 velocity = Clamp(std::abs(velocity), kMinVelocity, kMaxVelocity); | |
| 231 | |
| 232 start_time_ = current_time; | |
| 233 // This should never be less than 1 millisecond. | |
| 234 duration_ = base::TimeDelta::FromMilliseconds(0.15f + (velocity * 0.02f)); | |
| 235 | |
| 236 // The glow depends more on the velocity, and therefore starts out | |
| 237 // nearly invisible. | |
| 238 glow_alpha_start_ = 0.3f; | |
| 239 glow_scale_y_start_ = std::max(glow_scale_y_, 0.f); | |
| 240 | |
| 241 // Growth for the size of the glow should be quadratic to properly | |
| 242 // respond | |
| 243 // to a user's scrolling speed. The faster the scrolling speed, the more | |
| 244 // intense the effect should be for both the size and the saturation. | |
| 245 glow_scale_y_finish_ = std::min( | |
| 246 0.025f + (velocity * (velocity / 100) * 0.00015f) / 2.f, 1.f); | |
| 247 // Alpha should change for the glow as well as size. | |
| 248 glow_alpha_finish_ = Clamp(glow_alpha_start_, | |
| 249 velocity * kVelocityGlowFactor * .00001f, | |
| 250 kMaxAlpha); | |
| 251 target_displacement_ = 0.5; | |
| 252 } | |
| 253 | |
| 254 bool EdgeEffectL::Update(base::TimeTicks current_time) { | |
| 255 if (IsFinished()) | |
| 256 return false; | |
| 257 | |
| 258 const double dt = (current_time - start_time_).InMilliseconds(); | |
| 259 const double t = std::min(dt / duration_.InMilliseconds(), 1.); | |
| 260 const float interp = static_cast<float>(Damp(t, 1.)); | |
| 261 | |
| 262 glow_alpha_ = Lerp(glow_alpha_start_, glow_alpha_finish_, interp); | |
| 263 glow_scale_y_ = Lerp(glow_scale_y_start_, glow_scale_y_finish_, interp); | |
| 264 displacement_ = (displacement_ + target_displacement_) / 2.f; | |
| 265 | |
| 266 if (t >= 1.f - kEpsilon) { | |
| 267 switch (state_) { | |
| 268 case STATE_ABSORB: | |
| 269 state_ = STATE_RECEDE; | |
| 270 start_time_ = current_time; | |
| 271 duration_ = base::TimeDelta::FromMilliseconds(kRecedeTime); | |
| 272 | |
| 273 glow_alpha_start_ = glow_alpha_; | |
| 274 glow_scale_y_start_ = glow_scale_y_; | |
| 275 | |
| 276 glow_alpha_finish_ = 0.f; | |
| 277 glow_scale_y_finish_ = 0.f; | |
| 278 break; | |
| 279 case STATE_PULL: | |
| 280 // Hold in this state until explicitly released. | |
| 281 break; | |
| 282 case STATE_PULL_DECAY: | |
| 283 state_ = STATE_RECEDE; | |
| 284 break; | |
| 285 case STATE_RECEDE: | |
| 286 Finish(); | |
| 287 break; | |
| 288 default: | |
| 289 break; | |
| 290 } | |
| 291 } | |
| 292 | |
| 293 bool one_last_frame = false; | |
| 294 if (state_ == STATE_RECEDE && glow_scale_y_ <= 0) { | |
| 295 Finish(); | |
| 296 // TODO(jdduke): Check |one_last_frame_| behavior. | |
| 297 one_last_frame = true; | |
| 298 } | |
| 299 | |
| 300 return !IsFinished() || one_last_frame; | |
| 301 } | |
| 302 | |
| 303 void EdgeEffectL::ApplyToLayers(const gfx::SizeF& size, | |
| 304 const gfx::Transform& transform) { | |
| 305 if (IsFinished()) | |
| 306 return; | |
| 307 | |
| 308 // An empty window size, while meaningless, is also relatively harmless, and | |
| 309 // will simply prevent any drawing of the layers. | |
| 310 if (size.IsEmpty()) { | |
| 311 DisableLayer(glow_.get()); | |
| 312 return; | |
| 313 } | |
| 314 | |
| 315 const float r = size.width() * 0.75f / kSin; | |
| 316 const float y = kCos * r; | |
| 317 const float h = r - y; | |
| 318 bounds_ = gfx::Size(size.width(), (int) std::min(size.height(), h)); | |
| 319 gfx::Size image_bounds(r, std::min(1.f, glow_scale_y_) * bounds_.height()); | |
| 320 | |
| 321 glow_->SetIsDrawable(true); | |
| 322 glow_->SetTransformOrigin(gfx::Point3F(bounds_.width() * 0.5f, 0, 0)); | |
| 323 glow_->SetBounds(image_bounds); | |
| 324 glow_->SetOpacity(Clamp(glow_alpha_, 0.f, 1.f)); | |
| 325 | |
| 326 const float displacement = Clamp(displacement_, 0.f, 1.f) - 0.5f; | |
| 327 const float displacement_offset_x = bounds_.width() * displacement * 0.5f; | |
| 328 const float image_offset_x = (bounds_.width() - image_bounds.width()) * 0.5f; | |
| 329 gfx::Transform offset_transform; | |
| 330 offset_transform.Translate(image_offset_x - displacement_offset_x, 0); | |
| 331 offset_transform.ConcatTransform(transform); | |
| 332 glow_->SetTransform(offset_transform); | |
| 333 } | |
| 334 | |
| 335 // static | |
| 336 void EdgeEffectL::EnsureResources() { | |
| 337 g_edge_effect_resources.Get(); | |
| 338 } | |
| 339 | |
| 340 } // namespace content | |
| 341 | |
| OLD | NEW |