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