WebKit Roll 139512:139548
[chromium-blink-merge.git] / cc / render_surface_filters.cc
blobfe4fac75358745ddba7553aa3a175fe689ff7afc
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.
5 #include "cc/render_surface_filters.h"
7 #include "base/logging.h"
8 #include "skia/ext/refptr.h"
9 #include "third_party/WebKit/Source/Platform/chromium/public/WebFilterOperation.h"
10 #include "third_party/WebKit/Source/Platform/chromium/public/WebFilterOperations.h"
11 #include "third_party/WebKit/Source/Platform/chromium/public/WebGraphicsContext3D.h"
12 #include "third_party/skia/include/core/SkCanvas.h"
13 #include "third_party/skia/include/effects/SkBlurImageFilter.h"
14 #include "third_party/skia/include/effects/SkColorMatrixFilter.h"
15 #include "third_party/skia/include/effects/SkMagnifierImageFilter.h"
16 #include "third_party/skia/include/gpu/SkGpuDevice.h"
17 #include "third_party/skia/include/gpu/SkGrPixelRef.h"
18 #include "ui/gfx/size_f.h"
20 namespace cc {
22 namespace {
24 void getBrightnessMatrix(float amount, SkScalar matrix[20])
26 memset(matrix, 0, 20 * sizeof(SkScalar));
27 // Old implementation, a la the draft spec, a straight-up scale,
28 // representing <feFunc[R|G|B] type="linear" slope="[amount]">
29 // (See http://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html#brightnessEquivalent)
30 // matrix[0] = matrix[6] = matrix[12] = amount;
31 // matrix[18] = 1;
32 // New implementation, a translation in color space, representing
33 // <feFunc[R|G|B] type="linear" intercept="[amount]"/>
34 // (See https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647)
35 matrix[0] = matrix[6] = matrix[12] = matrix[18] = 1;
36 matrix[4] = matrix[9] = matrix[14] = amount * 255;
39 void getContrastMatrix(float amount, SkScalar matrix[20])
41 memset(matrix, 0, 20 * sizeof(SkScalar));
42 matrix[0] = matrix[6] = matrix[12] = amount;
43 matrix[4] = matrix[9] = matrix[14] = (-0.5f * amount + 0.5f) * 255;
44 matrix[18] = 1;
47 void getSaturateMatrix(float amount, SkScalar matrix[20])
49 // Note, these values are computed to ensure matrixNeedsClamping is false
50 // for amount in [0..1]
51 matrix[0] = 0.213f + 0.787f * amount;
52 matrix[1] = 0.715f - 0.715f * amount;
53 matrix[2] = 1.f - (matrix[0] + matrix[1]);
54 matrix[3] = matrix[4] = 0;
55 matrix[5] = 0.213f - 0.213f * amount;
56 matrix[6] = 0.715f + 0.285f * amount;
57 matrix[7] = 1.f - (matrix[5] + matrix[6]);
58 matrix[8] = matrix[9] = 0;
59 matrix[10] = 0.213f - 0.213f * amount;
60 matrix[11] = 0.715f - 0.715f * amount;
61 matrix[12] = 1.f - (matrix[10] + matrix[11]);
62 matrix[13] = matrix[14] = 0;
63 matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0;
64 matrix[18] = 1;
67 void getHueRotateMatrix(float hue, SkScalar matrix[20])
69 const float kPi = 3.1415926535897932384626433832795f;
71 float cosHue = cosf(hue * kPi / 180);
72 float sinHue = sinf(hue * kPi / 180);
73 matrix[0] = 0.213f + cosHue * 0.787f - sinHue * 0.213f;
74 matrix[1] = 0.715f - cosHue * 0.715f - sinHue * 0.715f;
75 matrix[2] = 0.072f - cosHue * 0.072f + sinHue * 0.928f;
76 matrix[3] = matrix[4] = 0;
77 matrix[5] = 0.213f - cosHue * 0.213f + sinHue * 0.143f;
78 matrix[6] = 0.715f + cosHue * 0.285f + sinHue * 0.140f;
79 matrix[7] = 0.072f - cosHue * 0.072f - sinHue * 0.283f;
80 matrix[8] = matrix[9] = 0;
81 matrix[10] = 0.213f - cosHue * 0.213f - sinHue * 0.787f;
82 matrix[11] = 0.715f - cosHue * 0.715f + sinHue * 0.715f;
83 matrix[12] = 0.072f + cosHue * 0.928f + sinHue * 0.072f;
84 matrix[13] = matrix[14] = 0;
85 matrix[15] = matrix[16] = matrix[17] = 0;
86 matrix[18] = 1;
87 matrix[19] = 0;
90 void getInvertMatrix(float amount, SkScalar matrix[20])
92 memset(matrix, 0, 20 * sizeof(SkScalar));
93 matrix[0] = matrix[6] = matrix[12] = 1 - 2 * amount;
94 matrix[4] = matrix[9] = matrix[14] = amount * 255;
95 matrix[18] = 1;
98 void getOpacityMatrix(float amount, SkScalar matrix[20])
100 memset(matrix, 0, 20 * sizeof(SkScalar));
101 matrix[0] = matrix[6] = matrix[12] = 1;
102 matrix[18] = amount;
105 void getGrayscaleMatrix(float amount, SkScalar matrix[20])
107 // Note, these values are computed to ensure matrixNeedsClamping is false
108 // for amount in [0..1]
109 matrix[0] = 0.2126f + 0.7874f * amount;
110 matrix[1] = 0.7152f - 0.7152f * amount;
111 matrix[2] = 1.f - (matrix[0] + matrix[1]);
112 matrix[3] = matrix[4] = 0;
114 matrix[5] = 0.2126f - 0.2126f * amount;
115 matrix[6] = 0.7152f + 0.2848f * amount;
116 matrix[7] = 1.f - (matrix[5] + matrix[6]);
117 matrix[8] = matrix[9] = 0;
119 matrix[10] = 0.2126f - 0.2126f * amount;
120 matrix[11] = 0.7152f - 0.7152f * amount;
121 matrix[12] = 1.f - (matrix[10] + matrix[11]);
122 matrix[13] = matrix[14] = 0;
124 matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0;
125 matrix[18] = 1;
128 void getSepiaMatrix(float amount, SkScalar matrix[20])
130 matrix[0] = 0.393f + 0.607f * amount;
131 matrix[1] = 0.769f - 0.769f * amount;
132 matrix[2] = 0.189f - 0.189f * amount;
133 matrix[3] = matrix[4] = 0;
135 matrix[5] = 0.349f - 0.349f * amount;
136 matrix[6] = 0.686f + 0.314f * amount;
137 matrix[7] = 0.168f - 0.168f * amount;
138 matrix[8] = matrix[9] = 0;
140 matrix[10] = 0.272f - 0.272f * amount;
141 matrix[11] = 0.534f - 0.534f * amount;
142 matrix[12] = 0.131f + 0.869f * amount;
143 matrix[13] = matrix[14] = 0;
145 matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0;
146 matrix[18] = 1;
149 // The 5x4 matrix is really a "compressed" version of a 5x5 matrix that'd have
150 // (0 0 0 0 1) as a last row, and that would be applied to a 5-vector extended
151 // from the 4-vector color with a 1.
152 void multColorMatrix(SkScalar a[20], SkScalar b[20], SkScalar out[20])
154 for (int j = 0; j < 4; ++j) {
155 for (int i = 0; i < 5; ++i) {
156 out[i+j*5] = i == 4 ? a[4+j*5] : 0;
157 for (int k = 0; k < 4; ++k)
158 out[i+j*5] += a[k+j*5] * b[i+k*5];
163 // To detect if we need to apply clamping after applying a matrix, we check if
164 // any output component might go outside of [0, 255] for any combination of
165 // input components in [0..255].
166 // Each output component is an affine transformation of the input component, so
167 // the minimum and maximum values are for any combination of minimum or maximum
168 // values of input components (i.e. 0 or 255).
169 // E.g. if R' = x*R + y*G + z*B + w*A + t
170 // Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the
171 // minimum value will be for R=0 if x>0 or R=255 if x<0.
172 // Same goes for all components.
173 bool componentNeedsClamping(SkScalar row[5])
175 SkScalar maxValue = row[4] / 255;
176 SkScalar minValue = row[4] / 255;
177 for (int i = 0; i < 4; ++i) {
178 if (row[i] > 0)
179 maxValue += row[i];
180 else
181 minValue += row[i];
183 return (maxValue > 1) || (minValue < 0);
186 bool matrixNeedsClamping(SkScalar matrix[20])
188 return componentNeedsClamping(matrix)
189 || componentNeedsClamping(matrix+5)
190 || componentNeedsClamping(matrix+10)
191 || componentNeedsClamping(matrix+15);
194 bool getColorMatrix(const WebKit::WebFilterOperation& op, SkScalar matrix[20])
196 switch (op.type()) {
197 case WebKit::WebFilterOperation::FilterTypeBrightness: {
198 getBrightnessMatrix(op.amount(), matrix);
199 return true;
201 case WebKit::WebFilterOperation::FilterTypeContrast: {
202 getContrastMatrix(op.amount(), matrix);
203 return true;
205 case WebKit::WebFilterOperation::FilterTypeGrayscale: {
206 getGrayscaleMatrix(1 - op.amount(), matrix);
207 return true;
209 case WebKit::WebFilterOperation::FilterTypeSepia: {
210 getSepiaMatrix(1 - op.amount(), matrix);
211 return true;
213 case WebKit::WebFilterOperation::FilterTypeSaturate: {
214 getSaturateMatrix(op.amount(), matrix);
215 return true;
217 case WebKit::WebFilterOperation::FilterTypeHueRotate: {
218 getHueRotateMatrix(op.amount(), matrix);
219 return true;
221 case WebKit::WebFilterOperation::FilterTypeInvert: {
222 getInvertMatrix(op.amount(), matrix);
223 return true;
225 case WebKit::WebFilterOperation::FilterTypeOpacity: {
226 getOpacityMatrix(op.amount(), matrix);
227 return true;
229 case WebKit::WebFilterOperation::FilterTypeColorMatrix: {
230 memcpy(matrix, op.matrix(), sizeof(SkScalar[20]));
231 return true;
233 default:
234 return false;
238 class FilterBufferState {
239 public:
240 FilterBufferState(GrContext* grContext, const gfx::SizeF& size, unsigned textureId)
241 : m_grContext(grContext)
242 , m_currentTexture(0)
244 // Wrap the source texture in a Ganesh platform texture.
245 GrPlatformTextureDesc platformTextureDescription;
246 platformTextureDescription.fWidth = size.width();
247 platformTextureDescription.fHeight = size.height();
248 platformTextureDescription.fConfig = kSkia8888_GrPixelConfig;
249 platformTextureDescription.fTextureHandle = textureId;
250 skia::RefPtr<GrTexture> texture = skia::AdoptRef(grContext->createPlatformTexture(platformTextureDescription));
251 // Place the platform texture inside an SkBitmap.
252 m_source.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
253 skia::RefPtr<SkGrPixelRef> pixelRef = skia::AdoptRef(new SkGrPixelRef(texture.get()));
254 m_source.setPixelRef(pixelRef.get());
257 ~FilterBufferState() { }
259 bool init(int filterCount)
261 int scratchCount = std::min(2, filterCount);
262 GrTextureDesc desc;
263 desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
264 desc.fSampleCnt = 0;
265 desc.fWidth = m_source.width();
266 desc.fHeight = m_source.height();
267 desc.fConfig = kSkia8888_GrPixelConfig;
268 for (int i = 0; i < scratchCount; ++i) {
269 GrAutoScratchTexture scratchTexture(m_grContext, desc, GrContext::kExact_ScratchTexMatch);
270 m_scratchTextures[i] = skia::AdoptRef(scratchTexture.detach());
271 if (!m_scratchTextures[i])
272 return false;
274 return true;
277 SkCanvas* canvas()
279 if (!m_canvas.get())
280 createCanvas();
281 return m_canvas.get();
284 const SkBitmap& source() { return m_source; }
286 void swap()
288 m_canvas->flush();
289 m_canvas.clear();
290 m_device.clear();
292 skia::RefPtr<SkGrPixelRef> pixelRef = skia::AdoptRef(new SkGrPixelRef(m_scratchTextures[m_currentTexture].get()));
293 m_source.setPixelRef(pixelRef.get());
294 m_currentTexture = 1 - m_currentTexture;
297 private:
298 void createCanvas()
300 DCHECK(m_scratchTextures[m_currentTexture].get());
301 m_device = skia::AdoptRef(new SkGpuDevice(m_grContext, m_scratchTextures[m_currentTexture].get()));
302 m_canvas = skia::AdoptRef(new SkCanvas(m_device.get()));
303 m_canvas->clear(0x0);
306 GrContext* m_grContext;
307 SkBitmap m_source;
308 skia::RefPtr<GrTexture> m_scratchTextures[2];
309 int m_currentTexture;
310 skia::RefPtr<SkGpuDevice> m_device;
311 skia::RefPtr<SkCanvas> m_canvas;
314 } // namespace
316 WebKit::WebFilterOperations RenderSurfaceFilters::optimize(const WebKit::WebFilterOperations& filters)
318 WebKit::WebFilterOperations newList;
320 SkScalar accumulatedColorMatrix[20];
321 bool haveAccumulatedColorMatrix = false;
322 for (unsigned i = 0; i < filters.size(); ++i) {
323 const WebKit::WebFilterOperation& op = filters.at(i);
325 // If the filter is a color matrix, we may be able to combine it with
326 // following filter(s) that also are color matrices.
327 SkScalar matrix[20];
328 if (getColorMatrix(op, matrix)) {
329 if (haveAccumulatedColorMatrix) {
330 SkScalar newMatrix[20];
331 multColorMatrix(matrix, accumulatedColorMatrix, newMatrix);
332 memcpy(accumulatedColorMatrix, newMatrix, sizeof(accumulatedColorMatrix));
333 } else {
334 memcpy(accumulatedColorMatrix, matrix, sizeof(accumulatedColorMatrix));
335 haveAccumulatedColorMatrix = true;
338 // We can only combine matrices if clamping of color components
339 // would have no effect.
340 if (!matrixNeedsClamping(accumulatedColorMatrix))
341 continue;
344 if (haveAccumulatedColorMatrix)
345 newList.append(WebKit::WebFilterOperation::createColorMatrixFilter(accumulatedColorMatrix));
346 haveAccumulatedColorMatrix = false;
348 switch (op.type()) {
349 case WebKit::WebFilterOperation::FilterTypeBlur:
350 case WebKit::WebFilterOperation::FilterTypeDropShadow:
351 case WebKit::WebFilterOperation::FilterTypeZoom:
352 newList.append(op);
353 break;
354 case WebKit::WebFilterOperation::FilterTypeBrightness:
355 case WebKit::WebFilterOperation::FilterTypeContrast:
356 case WebKit::WebFilterOperation::FilterTypeGrayscale:
357 case WebKit::WebFilterOperation::FilterTypeSepia:
358 case WebKit::WebFilterOperation::FilterTypeSaturate:
359 case WebKit::WebFilterOperation::FilterTypeHueRotate:
360 case WebKit::WebFilterOperation::FilterTypeInvert:
361 case WebKit::WebFilterOperation::FilterTypeOpacity:
362 case WebKit::WebFilterOperation::FilterTypeColorMatrix:
363 break;
366 if (haveAccumulatedColorMatrix)
367 newList.append(WebKit::WebFilterOperation::createColorMatrixFilter(accumulatedColorMatrix));
368 return newList;
371 SkBitmap RenderSurfaceFilters::apply(const WebKit::WebFilterOperations& filters, unsigned textureId, const gfx::SizeF& size, WebKit::WebGraphicsContext3D* context3D, GrContext* grContext)
373 if (!context3D || !grContext)
374 return SkBitmap();
376 WebKit::WebFilterOperations optimizedFilters = optimize(filters);
377 FilterBufferState state(grContext, size, textureId);
378 if (!state.init(optimizedFilters.size()))
379 return SkBitmap();
381 for (unsigned i = 0; i < optimizedFilters.size(); ++i) {
382 const WebKit::WebFilterOperation& op = optimizedFilters.at(i);
383 SkCanvas* canvas = state.canvas();
384 switch (op.type()) {
385 case WebKit::WebFilterOperation::FilterTypeColorMatrix: {
386 SkPaint paint;
387 skia::RefPtr<SkColorMatrixFilter> filter = skia::AdoptRef(new SkColorMatrixFilter(op.matrix()));
388 paint.setColorFilter(filter.get());
389 canvas->drawBitmap(state.source(), 0, 0, &paint);
390 break;
392 case WebKit::WebFilterOperation::FilterTypeBlur: {
393 float stdDeviation = op.amount();
394 skia::RefPtr<SkImageFilter> filter = skia::AdoptRef(new SkBlurImageFilter(stdDeviation, stdDeviation));
395 SkPaint paint;
396 paint.setImageFilter(filter.get());
397 canvas->drawSprite(state.source(), 0, 0, &paint);
398 break;
400 case WebKit::WebFilterOperation::FilterTypeDropShadow: {
401 skia::RefPtr<SkImageFilter> blurFilter = skia::AdoptRef(new SkBlurImageFilter(op.amount(), op.amount()));
402 skia::RefPtr<SkColorFilter> colorFilter = skia::AdoptRef(SkColorFilter::CreateModeFilter(op.dropShadowColor(), SkXfermode::kSrcIn_Mode));
403 SkPaint paint;
404 paint.setImageFilter(blurFilter.get());
405 paint.setColorFilter(colorFilter.get());
406 paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
407 canvas->saveLayer(0, &paint);
408 canvas->drawBitmap(state.source(), op.dropShadowOffset().x, -op.dropShadowOffset().y);
409 canvas->restore();
410 canvas->drawBitmap(state.source(), 0, 0);
411 break;
413 case WebKit::WebFilterOperation::FilterTypeZoom: {
414 SkPaint paint;
415 skia::RefPtr<SkImageFilter> zoomFilter = skia::AdoptRef(
416 new SkMagnifierImageFilter(
417 SkRect::MakeXYWH(op.zoomRect().x,
418 op.zoomRect().y,
419 op.zoomRect().width,
420 op.zoomRect().height),
421 op.amount()));
422 paint.setImageFilter(zoomFilter.get());
423 canvas->saveLayer(0, &paint);
424 canvas->drawBitmap(state.source(), 0, 0);
425 canvas->restore();
426 break;
428 case WebKit::WebFilterOperation::FilterTypeBrightness:
429 case WebKit::WebFilterOperation::FilterTypeContrast:
430 case WebKit::WebFilterOperation::FilterTypeGrayscale:
431 case WebKit::WebFilterOperation::FilterTypeSepia:
432 case WebKit::WebFilterOperation::FilterTypeSaturate:
433 case WebKit::WebFilterOperation::FilterTypeHueRotate:
434 case WebKit::WebFilterOperation::FilterTypeInvert:
435 case WebKit::WebFilterOperation::FilterTypeOpacity:
436 NOTREACHED();
437 break;
439 state.swap();
441 context3D->flush();
442 return state.source();
445 } // namespace cc