1eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// Copyright 2013 The Chromium Authors. All rights reserved.
2eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// Use of this source code is governed by a BSD-style license that can be
3eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// found in the LICENSE file.
4eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
5eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include <cmath>
6eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
7eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "cc/output/filter_operations.h"
8eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
9ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch#include "base/values.h"
10ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch#include "cc/output/filter_operation.h"
11ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
12eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochnamespace cc {
13eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
14eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochFilterOperations::FilterOperations() {}
15eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
16eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochFilterOperations::FilterOperations(const FilterOperations& other)
17eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    : operations_(other.operations_) {}
18eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
19eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochFilterOperations::~FilterOperations() {}
20eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
21eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochFilterOperations& FilterOperations::operator=(const FilterOperations& other) {
22eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  operations_ = other.operations_;
23eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return *this;
24eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
25eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
26eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool FilterOperations::operator==(const FilterOperations& other) const {
27eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (other.size() != size())
28eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return false;
29eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  for (size_t i = 0; i < size(); ++i) {
30eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (other.at(i) != at(i))
31eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return false;
32eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
33eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return true;
34eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
35eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
36eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid FilterOperations::Append(const FilterOperation& filter) {
37eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  operations_.push_back(filter);
38eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
39eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
40eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid FilterOperations::Clear() {
41eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  operations_.clear();
42eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
43eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
44eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool FilterOperations::IsEmpty() const {
45eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return operations_.empty();
46eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
47eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
48eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochstatic int SpreadForStdDeviation(float std_deviation) {
49eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html#feGaussianBlurElement
50eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // provides this approximation for evaluating a gaussian blur by a triple box
51eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // filter.
52eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  float d = floorf(std_deviation * 3.f * sqrt(8.f * atan(1.f)) / 4.f + 0.5f);
53eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return static_cast<int>(ceilf(d * 3.f / 2.f));
54eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
55eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
56eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid FilterOperations::GetOutsets(int* top,
57eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                  int* right,
58eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                  int* bottom,
59eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                  int* left) const {
60eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  *top = *right = *bottom = *left = 0;
61eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  for (size_t i = 0; i < operations_.size(); ++i) {
6268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    const FilterOperation& op = operations_[i];
6368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    // TODO(ajuma): Add support for reference filters once SkImageFilter
6468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    // reports its outsets.
6568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    DCHECK(op.type() != FilterOperation::REFERENCE);
66eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (op.type() == FilterOperation::BLUR ||
67eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        op.type() == FilterOperation::DROP_SHADOW) {
68eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      int spread = SpreadForStdDeviation(op.amount());
69eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      if (op.type() == FilterOperation::BLUR) {
70eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *top += spread;
71eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *right += spread;
72eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *bottom += spread;
73eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *left += spread;
74eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      } else {
75eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *top += spread - op.drop_shadow_offset().y();
76eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *right += spread + op.drop_shadow_offset().x();
77eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *bottom += spread + op.drop_shadow_offset().y();
78eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *left += spread - op.drop_shadow_offset().x();
79eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      }
80eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    }
81eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
82eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
83eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
84eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool FilterOperations::HasFilterThatMovesPixels() const {
85eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  for (size_t i = 0; i < operations_.size(); ++i) {
8668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    const FilterOperation& op = operations_[i];
8768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    // TODO(ajuma): Once SkImageFilter reports its outsets, use those here to
8868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    // determine whether a reference filter really moves pixels.
89eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    switch (op.type()) {
90eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case FilterOperation::BLUR:
91eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case FilterOperation::DROP_SHADOW:
92eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case FilterOperation::ZOOM:
9368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      case FilterOperation::REFERENCE:
94eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        return true;
95424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::OPACITY:
96424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::COLOR_MATRIX:
97424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::GRAYSCALE:
98424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::SEPIA:
99424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::SATURATE:
100424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::HUE_ROTATE:
101424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::INVERT:
102424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::BRIGHTNESS:
103424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::CONTRAST:
104424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::SATURATING_BRIGHTNESS:
1050529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      case FilterOperation::ALPHA_THRESHOLD:
106eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        break;
107eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    }
108eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
109eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return false;
110eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
111eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
112eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool FilterOperations::HasFilterThatAffectsOpacity() const {
113eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  for (size_t i = 0; i < operations_.size(); ++i) {
11468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    const FilterOperation& op = operations_[i];
11568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    // TODO(ajuma): Make this smarter for reference filters. Once SkImageFilter
11668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    // can report affectsOpacity(), call that.
117eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    switch (op.type()) {
118eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case FilterOperation::OPACITY:
119eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case FilterOperation::BLUR:
120eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case FilterOperation::DROP_SHADOW:
121eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case FilterOperation::ZOOM:
12268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      case FilterOperation::REFERENCE:
1230529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      case FilterOperation::ALPHA_THRESHOLD:
124eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        return true;
125eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      case FilterOperation::COLOR_MATRIX: {
126eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        const SkScalar* matrix = op.matrix();
127424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        if (matrix[15] ||
128424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)            matrix[16] ||
129424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)            matrix[17] ||
130424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)            matrix[18] != 1 ||
131424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)            matrix[19])
132424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)          return true;
133424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        break;
134eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      }
135424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::GRAYSCALE:
136424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::SEPIA:
137424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::SATURATE:
138424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::HUE_ROTATE:
139424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::INVERT:
140424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::BRIGHTNESS:
141424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::CONTRAST:
142424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      case FilterOperation::SATURATING_BRIGHTNESS:
143eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        break;
144eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    }
145eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
146eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return false;
147eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}
148eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
14968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)bool FilterOperations::HasReferenceFilter() const {
15068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  for (size_t i = 0; i < operations_.size(); ++i) {
15168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    if (operations_[i].type() == FilterOperation::REFERENCE)
15268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      return true;
15368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  }
15468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  return false;
15568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)}
15668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
1577dbb3d5cf0c15f500944d211057644d6a2f37371Ben MurdochFilterOperations FilterOperations::Blend(const FilterOperations& from,
1587dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                                         double progress) const {
159f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (HasReferenceFilter() || from.HasReferenceFilter())
160f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return *this;
1617dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
162f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  bool from_is_longer = from.size() > size();
163f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
164f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  size_t shorter_size, longer_size;
165f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (size() == from.size()) {
166f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    shorter_size = longer_size = size();
167f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  } else if  (from_is_longer) {
168f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    longer_size = from.size();
169f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    shorter_size = size();
170f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  } else {
171f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    longer_size = size();
172f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    shorter_size = from.size();
1737dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  }
1747dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
175f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  for (size_t i = 0; i < shorter_size; i++) {
1767dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    if (from.at(i).type() != at(i).type())
1777dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      return *this;
1787dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  }
1797dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
180f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  FilterOperations blended_filters;
181f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  for (size_t i = 0; i < shorter_size; i++) {
1827dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    blended_filters.Append(
1837dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        FilterOperation::Blend(&from.at(i), &at(i), progress));
1847dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  }
1857dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
186f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (from_is_longer) {
187f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    for (size_t i = shorter_size; i < longer_size; i++) {
188f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      blended_filters.Append(
189f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          FilterOperation::Blend(&from.at(i), NULL, progress));
190f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
191f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  } else {
192f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    for (size_t i = shorter_size; i < longer_size; i++)
193f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      blended_filters.Append(FilterOperation::Blend(NULL, &at(i), progress));
194f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
195f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1967dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  return blended_filters;
1977dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch}
1987dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
199ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdochscoped_ptr<base::Value> FilterOperations::AsValue() const {
2005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  scoped_ptr<base::ListValue> value(new base::ListValue);
201ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch  for (size_t i = 0; i < operations_.size(); ++i)
202ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    value->Append(operations_[i].AsValue().release());
203ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch  return value.PassAs<base::Value>();
204ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch}
205ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
206eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch}  // namespace cc
207