Home | History | Annotate | Download | only in output
      1 // Copyright 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 "cc/output/render_surface_filters.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/logging.h"
     10 #include "cc/output/filter_operation.h"
     11 #include "cc/output/filter_operations.h"
     12 #include "skia/ext/refptr.h"
     13 #include "third_party/skia/include/core/SkCanvas.h"
     14 #include "third_party/skia/include/effects/SkBlurImageFilter.h"
     15 #include "third_party/skia/include/effects/SkColorMatrixFilter.h"
     16 #include "third_party/skia/include/effects/SkMagnifierImageFilter.h"
     17 #include "third_party/skia/include/gpu/SkGpuDevice.h"
     18 #include "third_party/skia/include/gpu/SkGrPixelRef.h"
     19 #include "ui/gfx/size_f.h"
     20 
     21 namespace cc {
     22 
     23 namespace {
     24 
     25 void GetBrightnessMatrix(float amount, SkScalar matrix[20]) {
     26   // Spec implementation
     27   // (http://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html#brightnessEquivalent)
     28   // <feFunc[R|G|B] type="linear" slope="[amount]">
     29   memset(matrix, 0, 20 * sizeof(SkScalar));
     30   matrix[0] = matrix[6] = matrix[12] = amount;
     31   matrix[18] = 1.f;
     32 }
     33 
     34 void GetSaturatingBrightnessMatrix(float amount, SkScalar matrix[20]) {
     35   // Legacy implementation used by internal clients.
     36   // <feFunc[R|G|B] type="linear" intercept="[amount]"/>
     37   memset(matrix, 0, 20 * sizeof(SkScalar));
     38   matrix[0] = matrix[6] = matrix[12] = matrix[18] = 1.f;
     39   matrix[4] = matrix[9] = matrix[14] = amount * 255.f;
     40 }
     41 
     42 void GetContrastMatrix(float amount, SkScalar matrix[20]) {
     43   memset(matrix, 0, 20 * sizeof(SkScalar));
     44   matrix[0] = matrix[6] = matrix[12] = amount;
     45   matrix[4] = matrix[9] = matrix[14] = (-0.5f * amount + 0.5f) * 255.f;
     46   matrix[18] = 1.f;
     47 }
     48 
     49 void GetSaturateMatrix(float amount, SkScalar matrix[20]) {
     50   // Note, these values are computed to ensure MatrixNeedsClamping is false
     51   // for amount in [0..1]
     52   matrix[0] = 0.213f + 0.787f * amount;
     53   matrix[1] = 0.715f - 0.715f * amount;
     54   matrix[2] = 1.f - (matrix[0] + matrix[1]);
     55   matrix[3] = matrix[4] = 0.f;
     56   matrix[5] = 0.213f - 0.213f * amount;
     57   matrix[6] = 0.715f + 0.285f * amount;
     58   matrix[7] = 1.f - (matrix[5] + matrix[6]);
     59   matrix[8] = matrix[9] = 0.f;
     60   matrix[10] = 0.213f - 0.213f * amount;
     61   matrix[11] = 0.715f - 0.715f * amount;
     62   matrix[12] = 1.f - (matrix[10] + matrix[11]);
     63   matrix[13] = matrix[14] = 0.f;
     64   matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0.f;
     65   matrix[18] = 1.f;
     66 }
     67 
     68 void GetHueRotateMatrix(float hue, SkScalar matrix[20]) {
     69   const float kPi = 3.1415926535897932384626433832795f;
     70 
     71   float cos_hue = cosf(hue * kPi / 180.f);
     72   float sin_hue = sinf(hue * kPi / 180.f);
     73   matrix[0] = 0.213f + cos_hue * 0.787f - sin_hue * 0.213f;
     74   matrix[1] = 0.715f - cos_hue * 0.715f - sin_hue * 0.715f;
     75   matrix[2] = 0.072f - cos_hue * 0.072f + sin_hue * 0.928f;
     76   matrix[3] = matrix[4] = 0.f;
     77   matrix[5] = 0.213f - cos_hue * 0.213f + sin_hue * 0.143f;
     78   matrix[6] = 0.715f + cos_hue * 0.285f + sin_hue * 0.140f;
     79   matrix[7] = 0.072f - cos_hue * 0.072f - sin_hue * 0.283f;
     80   matrix[8] = matrix[9] = 0.f;
     81   matrix[10] = 0.213f - cos_hue * 0.213f - sin_hue * 0.787f;
     82   matrix[11] = 0.715f - cos_hue * 0.715f + sin_hue * 0.715f;
     83   matrix[12] = 0.072f + cos_hue * 0.928f + sin_hue * 0.072f;
     84   matrix[13] = matrix[14] = 0.f;
     85   matrix[15] = matrix[16] = matrix[17] = 0.f;
     86   matrix[18] = 1.f;
     87   matrix[19] = 0.f;
     88 }
     89 
     90 void GetInvertMatrix(float amount, SkScalar matrix[20]) {
     91   memset(matrix, 0, 20 * sizeof(SkScalar));
     92   matrix[0] = matrix[6] = matrix[12] = 1.f - 2.f * amount;
     93   matrix[4] = matrix[9] = matrix[14] = amount * 255.f;
     94   matrix[18] = 1.f;
     95 }
     96 
     97 void GetOpacityMatrix(float amount, SkScalar matrix[20]) {
     98   memset(matrix, 0, 20 * sizeof(SkScalar));
     99   matrix[0] = matrix[6] = matrix[12] = 1.f;
    100   matrix[18] = amount;
    101 }
    102 
    103 void GetGrayscaleMatrix(float amount, SkScalar matrix[20]) {
    104   // Note, these values are computed to ensure MatrixNeedsClamping is false
    105   // for amount in [0..1]
    106   matrix[0] = 0.2126f + 0.7874f * amount;
    107   matrix[1] = 0.7152f - 0.7152f * amount;
    108   matrix[2] = 1.f - (matrix[0] + matrix[1]);
    109   matrix[3] = matrix[4] = 0.f;
    110 
    111   matrix[5] = 0.2126f - 0.2126f * amount;
    112   matrix[6] = 0.7152f + 0.2848f * amount;
    113   matrix[7] = 1.f - (matrix[5] + matrix[6]);
    114   matrix[8] = matrix[9] = 0.f;
    115 
    116   matrix[10] = 0.2126f - 0.2126f * amount;
    117   matrix[11] = 0.7152f - 0.7152f * amount;
    118   matrix[12] = 1.f - (matrix[10] + matrix[11]);
    119   matrix[13] = matrix[14] = 0.f;
    120 
    121   matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0.f;
    122   matrix[18] = 1.f;
    123 }
    124 
    125 void GetSepiaMatrix(float amount, SkScalar matrix[20]) {
    126   matrix[0] = 0.393f + 0.607f * amount;
    127   matrix[1] = 0.769f - 0.769f * amount;
    128   matrix[2] = 0.189f - 0.189f * amount;
    129   matrix[3] = matrix[4] = 0.f;
    130 
    131   matrix[5] = 0.349f - 0.349f * amount;
    132   matrix[6] = 0.686f + 0.314f * amount;
    133   matrix[7] = 0.168f - 0.168f * amount;
    134   matrix[8] = matrix[9] = 0.f;
    135 
    136   matrix[10] = 0.272f - 0.272f * amount;
    137   matrix[11] = 0.534f - 0.534f * amount;
    138   matrix[12] = 0.131f + 0.869f * amount;
    139   matrix[13] = matrix[14] = 0.f;
    140 
    141   matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0.f;
    142   matrix[18] = 1.f;
    143 }
    144 
    145 // The 5x4 matrix is really a "compressed" version of a 5x5 matrix that'd have
    146 // (0 0 0 0 1) as a last row, and that would be applied to a 5-vector extended
    147 // from the 4-vector color with a 1.
    148 void MultColorMatrix(SkScalar a[20], SkScalar b[20], SkScalar out[20]) {
    149   for (int j = 0; j < 4; ++j) {
    150     for (int i = 0; i < 5; ++i) {
    151       out[i+j*5] = i == 4 ? a[4+j*5] : 0.f;
    152       for (int k = 0; k < 4; ++k)
    153         out[i+j*5] += a[k+j*5] * b[i+k*5];
    154     }
    155   }
    156 }
    157 
    158 // To detect if we need to apply clamping after applying a matrix, we check if
    159 // any output component might go outside of [0, 255] for any combination of
    160 // input components in [0..255].
    161 // Each output component is an affine transformation of the input component, so
    162 // the minimum and maximum values are for any combination of minimum or maximum
    163 // values of input components (i.e. 0 or 255).
    164 // E.g. if R' = x*R + y*G + z*B + w*A + t
    165 // Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the
    166 // minimum value will be for R=0 if x>0 or R=255 if x<0.
    167 // Same goes for all components.
    168 bool ComponentNeedsClamping(SkScalar row[5]) {
    169   SkScalar max_value = row[4] / 255.f;
    170   SkScalar min_value = row[4] / 255.f;
    171   for (int i = 0; i < 4; ++i) {
    172     if (row[i] > 0)
    173       max_value += row[i];
    174     else
    175       min_value += row[i];
    176   }
    177   return (max_value > 1.f) || (min_value < 0.f);
    178 }
    179 
    180 bool MatrixNeedsClamping(SkScalar matrix[20]) {
    181   return ComponentNeedsClamping(matrix)
    182       || ComponentNeedsClamping(matrix+5)
    183       || ComponentNeedsClamping(matrix+10)
    184       || ComponentNeedsClamping(matrix+15);
    185 }
    186 
    187 bool GetColorMatrix(const FilterOperation& op, SkScalar matrix[20]) {
    188   switch (op.type()) {
    189     case FilterOperation::BRIGHTNESS: {
    190       GetBrightnessMatrix(op.amount(), matrix);
    191       return true;
    192     }
    193     case FilterOperation::SATURATING_BRIGHTNESS: {
    194       GetSaturatingBrightnessMatrix(op.amount(), matrix);
    195       return true;
    196     }
    197     case FilterOperation::CONTRAST: {
    198       GetContrastMatrix(op.amount(), matrix);
    199       return true;
    200     }
    201     case FilterOperation::GRAYSCALE: {
    202       GetGrayscaleMatrix(1.f - op.amount(), matrix);
    203       return true;
    204     }
    205     case FilterOperation::SEPIA: {
    206       GetSepiaMatrix(1.f - op.amount(), matrix);
    207       return true;
    208     }
    209     case FilterOperation::SATURATE: {
    210       GetSaturateMatrix(op.amount(), matrix);
    211       return true;
    212     }
    213     case FilterOperation::HUE_ROTATE: {
    214       GetHueRotateMatrix(op.amount(), matrix);
    215       return true;
    216     }
    217     case FilterOperation::INVERT: {
    218       GetInvertMatrix(op.amount(), matrix);
    219       return true;
    220     }
    221     case FilterOperation::OPACITY: {
    222       GetOpacityMatrix(op.amount(), matrix);
    223       return true;
    224     }
    225     case FilterOperation::COLOR_MATRIX: {
    226       memcpy(matrix, op.matrix(), sizeof(SkScalar[20]));
    227       return true;
    228     }
    229     default:
    230       return false;
    231   }
    232 }
    233 
    234 class FilterBufferState {
    235  public:
    236   FilterBufferState(GrContext* gr_context,
    237                     gfx::SizeF size,
    238                     unsigned texture_id)
    239       : gr_context_(gr_context),
    240         current_texture_(0) {
    241     // Wrap the source texture in a Ganesh platform texture.
    242     GrBackendTextureDesc backend_texture_description;
    243     backend_texture_description.fWidth = size.width();
    244     backend_texture_description.fHeight = size.height();
    245     backend_texture_description.fConfig = kSkia8888_GrPixelConfig;
    246     backend_texture_description.fTextureHandle = texture_id;
    247     skia::RefPtr<GrTexture> texture = skia::AdoptRef(
    248         gr_context->wrapBackendTexture(backend_texture_description));
    249     // Place the platform texture inside an SkBitmap.
    250     source_.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
    251     skia::RefPtr<SkGrPixelRef> pixel_ref =
    252         skia::AdoptRef(new SkGrPixelRef(texture.get()));
    253     source_.setPixelRef(pixel_ref.get());
    254   }
    255 
    256   ~FilterBufferState() {}
    257 
    258   bool Init(int filter_count) {
    259     int scratch_count = std::min(2, filter_count);
    260     GrTextureDesc desc;
    261     desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
    262     desc.fSampleCnt = 0;
    263     desc.fWidth = source_.width();
    264     desc.fHeight = source_.height();
    265     desc.fConfig = kSkia8888_GrPixelConfig;
    266     for (int i = 0; i < scratch_count; ++i) {
    267       GrAutoScratchTexture scratch_texture(
    268           gr_context_, desc, GrContext::kExact_ScratchTexMatch);
    269       scratch_textures_[i] = skia::AdoptRef(scratch_texture.detach());
    270       if (!scratch_textures_[i])
    271         return false;
    272     }
    273     return true;
    274   }
    275 
    276   SkCanvas* Canvas() {
    277     if (!canvas_)
    278       CreateCanvas();
    279     return canvas_.get();
    280   }
    281 
    282   const SkBitmap& Source() { return source_; }
    283 
    284   void Swap() {
    285     canvas_->flush();
    286     canvas_.clear();
    287     device_.clear();
    288 
    289     skia::RefPtr<SkGrPixelRef> pixel_ref = skia::AdoptRef(
    290         new SkGrPixelRef(scratch_textures_[current_texture_].get()));
    291     source_.setPixelRef(pixel_ref.get());
    292     current_texture_ = 1 - current_texture_;
    293   }
    294 
    295  private:
    296   void CreateCanvas() {
    297     DCHECK(scratch_textures_[current_texture_].get());
    298     device_ = skia::AdoptRef(new SkGpuDevice(
    299         gr_context_, scratch_textures_[current_texture_].get()));
    300     canvas_ = skia::AdoptRef(new SkCanvas(device_.get()));
    301     canvas_->clear(0x0);
    302   }
    303 
    304   GrContext* gr_context_;
    305   SkBitmap source_;
    306   skia::RefPtr<GrTexture> scratch_textures_[2];
    307   int current_texture_;
    308   skia::RefPtr<SkGpuDevice> device_;
    309   skia::RefPtr<SkCanvas> canvas_;
    310 };
    311 
    312 }  // namespace
    313 
    314 FilterOperations RenderSurfaceFilters::Optimize(
    315     const FilterOperations& filters) {
    316   FilterOperations new_list;
    317 
    318   SkScalar accumulated_color_matrix[20];
    319   bool have_accumulated_color_matrix = false;
    320   for (unsigned i = 0; i < filters.size(); ++i) {
    321     const FilterOperation& op = filters.at(i);
    322 
    323     // If the filter is a color matrix, we may be able to combine it with
    324     // following Filter(s) that also are color matrices.
    325     SkScalar matrix[20];
    326     if (GetColorMatrix(op, matrix)) {
    327       if (have_accumulated_color_matrix) {
    328         SkScalar new_matrix[20];
    329         MultColorMatrix(matrix, accumulated_color_matrix, new_matrix);
    330         memcpy(accumulated_color_matrix,
    331                new_matrix,
    332                sizeof(accumulated_color_matrix));
    333       } else {
    334         memcpy(accumulated_color_matrix,
    335                matrix,
    336                sizeof(accumulated_color_matrix));
    337         have_accumulated_color_matrix = true;
    338       }
    339 
    340       // We can only combine matrices if clamping of color components
    341       // would have no effect.
    342       if (!MatrixNeedsClamping(accumulated_color_matrix))
    343         continue;
    344     }
    345 
    346     if (have_accumulated_color_matrix) {
    347       new_list.Append(FilterOperation::CreateColorMatrixFilter(
    348           accumulated_color_matrix));
    349     }
    350     have_accumulated_color_matrix = false;
    351 
    352     switch (op.type()) {
    353       case FilterOperation::BLUR:
    354       case FilterOperation::DROP_SHADOW:
    355       case FilterOperation::ZOOM:
    356         new_list.Append(op);
    357         break;
    358       case FilterOperation::BRIGHTNESS:
    359       case FilterOperation::SATURATING_BRIGHTNESS:
    360       case FilterOperation::CONTRAST:
    361       case FilterOperation::GRAYSCALE:
    362       case FilterOperation::SEPIA:
    363       case FilterOperation::SATURATE:
    364       case FilterOperation::HUE_ROTATE:
    365       case FilterOperation::INVERT:
    366       case FilterOperation::OPACITY:
    367       case FilterOperation::COLOR_MATRIX:
    368         break;
    369     }
    370   }
    371   if (have_accumulated_color_matrix) {
    372     new_list.Append(FilterOperation::CreateColorMatrixFilter(
    373         accumulated_color_matrix));
    374   }
    375   return new_list;
    376 }
    377 
    378 SkBitmap RenderSurfaceFilters::Apply(const FilterOperations& filters,
    379                                      unsigned texture_id,
    380                                      gfx::SizeF size,
    381                                      GrContext* gr_context) {
    382   DCHECK(gr_context);
    383 
    384   FilterBufferState state(gr_context, size, texture_id);
    385   if (!state.Init(filters.size()))
    386     return SkBitmap();
    387 
    388   for (unsigned i = 0; i < filters.size(); ++i) {
    389     const FilterOperation& op = filters.at(i);
    390     SkCanvas* canvas = state.Canvas();
    391     switch (op.type()) {
    392       case FilterOperation::COLOR_MATRIX: {
    393         SkPaint paint;
    394         skia::RefPtr<SkColorMatrixFilter> filter =
    395             skia::AdoptRef(new SkColorMatrixFilter(op.matrix()));
    396         paint.setColorFilter(filter.get());
    397         canvas->drawBitmap(state.Source(), 0, 0, &paint);
    398         break;
    399       }
    400       case FilterOperation::BLUR: {
    401         float std_deviation = op.amount();
    402         skia::RefPtr<SkImageFilter> filter =
    403             skia::AdoptRef(new SkBlurImageFilter(std_deviation, std_deviation));
    404         SkPaint paint;
    405         paint.setImageFilter(filter.get());
    406         canvas->drawSprite(state.Source(), 0, 0, &paint);
    407         break;
    408       }
    409       case FilterOperation::DROP_SHADOW: {
    410         skia::RefPtr<SkImageFilter> blur_filter =
    411             skia::AdoptRef(new SkBlurImageFilter(op.amount(), op.amount()));
    412         skia::RefPtr<SkColorFilter> color_filter =
    413             skia::AdoptRef(SkColorFilter::CreateModeFilter(
    414                 op.drop_shadow_color(), SkXfermode::kSrcIn_Mode));
    415         SkPaint paint;
    416         paint.setImageFilter(blur_filter.get());
    417         paint.setColorFilter(color_filter.get());
    418         paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
    419         canvas->saveLayer(NULL, &paint);
    420         canvas->drawBitmap(state.Source(),
    421                            op.drop_shadow_offset().x(),
    422                            op.drop_shadow_offset().y());
    423         canvas->restore();
    424         canvas->drawBitmap(state.Source(), 0, 0);
    425         break;
    426       }
    427       case FilterOperation::ZOOM: {
    428         SkPaint paint;
    429         int width = state.Source().width();
    430         int height = state.Source().height();
    431         skia::RefPtr<SkImageFilter> zoom_filter = skia::AdoptRef(
    432             new SkMagnifierImageFilter(
    433                 SkRect::MakeXYWH(
    434                     (width - (width / op.amount())) / 2.f,
    435                     (height - (height / op.amount())) / 2.f,
    436                     width / op.amount(),
    437                     height / op.amount()),
    438                 op.zoom_inset()));
    439         paint.setImageFilter(zoom_filter.get());
    440         canvas->saveLayer(NULL, &paint);
    441         canvas->drawBitmap(state.Source(), 0, 0);
    442         canvas->restore();
    443         break;
    444       }
    445       case FilterOperation::BRIGHTNESS:
    446       case FilterOperation::SATURATING_BRIGHTNESS:
    447       case FilterOperation::CONTRAST:
    448       case FilterOperation::GRAYSCALE:
    449       case FilterOperation::SEPIA:
    450       case FilterOperation::SATURATE:
    451       case FilterOperation::HUE_ROTATE:
    452       case FilterOperation::INVERT:
    453       case FilterOperation::OPACITY:
    454         NOTREACHED();
    455         break;
    456     }
    457     state.Swap();
    458   }
    459   return state.Source();
    460 }
    461 
    462 }  // namespace cc
    463