1/*
2 * Copyright 2006, The Android Open Source Project
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *  * Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 *  * Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#define LOG_TAG "PlatformGraphicsContext"
27#define LOG_NDEBUG 1
28
29#include "config.h"
30#include "PlatformGraphicsContext.h"
31
32#include "AndroidLog.h"
33#include "SkBlurDrawLooper.h"
34#include "SkBlurMaskFilter.h"
35#include "SkColorPriv.h"
36#include "SkDashPathEffect.h"
37#include "SkPaint.h"
38#include "SkShader.h"
39#include "SkiaUtils.h"
40
41namespace WebCore {
42
43//**************************************
44// Helper functions
45//**************************************
46
47static int RoundToInt(float x)
48{
49    return (int)roundf(x);
50}
51
52template <typename T> T* deepCopyPtr(const T* src)
53{
54    return src ? new T(*src) : 0;
55}
56
57// Set a bitmap shader that mimics dashing by width-on, width-off.
58// Returns false if it could not succeed (e.g. there was an existing shader)
59static bool setBitmapDash(SkPaint* paint, int width) {
60    if (width <= 0 || paint->getShader())
61        return false;
62
63    SkColor c = paint->getColor();
64
65    SkBitmap bm;
66    bm.setConfig(SkBitmap::kARGB_8888_Config, 2, 1);
67    bm.allocPixels();
68    bm.lockPixels();
69
70    // set the ON pixel
71    *bm.getAddr32(0, 0) = SkPreMultiplyARGB(0xFF, SkColorGetR(c),
72                                            SkColorGetG(c), SkColorGetB(c));
73    // set the OFF pixel
74    *bm.getAddr32(1, 0) = 0;
75    bm.unlockPixels();
76
77    SkMatrix matrix;
78    matrix.setScale(SkIntToScalar(width), SK_Scalar1);
79
80    SkShader* s = SkShader::CreateBitmapShader(bm, SkShader::kRepeat_TileMode,
81                                               SkShader::kClamp_TileMode);
82    s->setLocalMatrix(matrix);
83
84    paint->setShader(s)->unref();
85    paint->setFilterBitmap(true);
86    paint->setAntiAlias(true);
87    return true;
88}
89
90//**************************************
91// State implementation
92//**************************************
93
94PlatformGraphicsContext::State::State()
95    : pathEffect(0)
96    , miterLimit(4)
97    , alpha(1)
98    , strokeThickness(0) // Same as default in GraphicsContextPrivate.h
99    , lineCap(SkPaint::kDefault_Cap)
100    , lineJoin(SkPaint::kDefault_Join)
101    , mode(SkXfermode::kSrcOver_Mode)
102    , dashRatio(3)
103    , fillColor(SK_ColorBLACK)
104    , fillShader(0)
105    , strokeColor(SK_ColorBLACK)
106    , strokeShader(0)
107    , useAA(true)
108    , strokeStyle(SolidStroke)
109{
110}
111
112PlatformGraphicsContext::State::State(const State& other)
113    : pathEffect(other.pathEffect)
114    , miterLimit(other.miterLimit)
115    , alpha(other.alpha)
116    , strokeThickness(other.strokeThickness)
117    , lineCap(other.lineCap)
118    , lineJoin(other.lineJoin)
119    , mode(other.mode)
120    , dashRatio(other.dashRatio)
121    , shadow(other.shadow)
122    , fillColor(other.fillColor)
123    , fillShader(other.fillShader)
124    , strokeColor(other.strokeColor)
125    , strokeShader(other.strokeShader)
126    , useAA(other.useAA)
127    , strokeStyle(other.strokeStyle)
128{
129    SkSafeRef(pathEffect);
130    SkSafeRef(fillShader);
131    SkSafeRef(strokeShader);
132}
133
134PlatformGraphicsContext::State::~State()
135{
136    SkSafeUnref(pathEffect);
137    SkSafeUnref(fillShader);
138    SkSafeUnref(strokeShader);
139}
140
141void PlatformGraphicsContext::State::setShadow(int radius, int dx, int dy, SkColor c)
142{
143    // Cut the radius in half, to visually match the effect seen in
144    // safari browser
145    shadow.blur = SkScalarHalf(SkIntToScalar(radius));
146    shadow.dx = SkIntToScalar(dx);
147    shadow.dy = SkIntToScalar(dy);
148    shadow.color = c;
149}
150
151bool PlatformGraphicsContext::State::setupShadowPaint(SkPaint* paint, SkPoint* offset,
152                      bool shadowsIgnoreTransforms)
153{
154    paint->setAntiAlias(true);
155    paint->setDither(true);
156    paint->setXfermodeMode(mode);
157    paint->setColor(shadow.color);
158    offset->set(shadow.dx, shadow.dy);
159
160    // Currently, only GraphicsContexts associated with the
161    // HTMLCanvasElement have shadows ignore transforms set.  This
162    // allows us to distinguish between CSS and Canvas shadows which
163    // have different rendering specifications.
164    uint32_t flags = SkBlurMaskFilter::kNone_BlurFlag;
165    if (shadowsIgnoreTransforms) {
166        offset->fY = -offset->fY;
167        flags |= SkBlurMaskFilter::kIgnoreTransform_BlurFlag;
168    }
169
170    if (shadow.blur > 0) {
171        paint->setMaskFilter(SkBlurMaskFilter::Create(shadow.blur,
172                             SkBlurMaskFilter::kNormal_BlurStyle, flags))->unref();
173    }
174    return SkColorGetA(shadow.color) && (shadow.blur || shadow.dx || shadow.dy);
175}
176
177SkColor PlatformGraphicsContext::State::applyAlpha(SkColor c) const
178{
179    int s = RoundToInt(alpha * 256);
180    if (s >= 256)
181        return c;
182    if (s < 0)
183        return 0;
184
185    int a = SkAlphaMul(SkColorGetA(c), s);
186    return (c & 0x00FFFFFF) | (a << 24);
187}
188
189// Returns a new State with all of this object's inherited properties copied.
190PlatformGraphicsContext::State PlatformGraphicsContext::State::cloneInheritedProperties()
191{
192    return PlatformGraphicsContext::State(*this);
193}
194
195//**************************************
196// PlatformGraphicsContext
197//**************************************
198
199PlatformGraphicsContext::PlatformGraphicsContext()
200{
201    m_stateStack.append(State());
202    m_state = &m_stateStack.last();
203}
204
205PlatformGraphicsContext::~PlatformGraphicsContext()
206{
207}
208
209//**************************************
210// State management
211//**************************************
212
213void PlatformGraphicsContext::save()
214{
215    m_stateStack.append(m_state->cloneInheritedProperties());
216    m_state = &m_stateStack.last();
217}
218
219void PlatformGraphicsContext::restore()
220{
221    m_stateStack.removeLast();
222    m_state = &m_stateStack.last();
223}
224
225//**************************************
226// State setters
227//**************************************
228
229void PlatformGraphicsContext::setAlpha(float alpha)
230{
231    m_state->alpha = alpha;
232}
233
234int PlatformGraphicsContext::getNormalizedAlpha() const
235{
236    int alpha = roundf(m_state->alpha * 256);
237    if (alpha > 255)
238        alpha = 255;
239    else if (alpha < 0)
240        alpha = 0;
241    return alpha;
242}
243
244void PlatformGraphicsContext::setCompositeOperation(CompositeOperator op)
245{
246    m_state->mode = WebCoreCompositeToSkiaComposite(op);
247}
248
249bool PlatformGraphicsContext::setFillColor(const Color& c)
250{
251    bool dirty = false;
252    if (m_state->fillColor != c.rgb()) {
253        m_state->fillColor = c.rgb();
254        dirty = true;
255    }
256    return setFillShader(0) || dirty;
257}
258
259bool PlatformGraphicsContext::setFillShader(SkShader* fillShader)
260{
261    bool dirty = false;
262    if (fillShader && m_state->fillColor != Color::black) {
263        m_state->fillColor = Color::black;
264        dirty = true;
265    }
266
267    if (fillShader != m_state->fillShader) {
268        SkSafeUnref(m_state->fillShader);
269        m_state->fillShader = fillShader;
270        SkSafeRef(m_state->fillShader);
271        dirty = true;
272    }
273    return dirty;
274}
275
276void PlatformGraphicsContext::setLineCap(LineCap cap)
277{
278    switch (cap) {
279    case ButtCap:
280        m_state->lineCap = SkPaint::kButt_Cap;
281        break;
282    case RoundCap:
283        m_state->lineCap = SkPaint::kRound_Cap;
284        break;
285    case SquareCap:
286        m_state->lineCap = SkPaint::kSquare_Cap;
287        break;
288    default:
289        ALOGD("PlatformGraphicsContextSkia::setLineCap: unknown LineCap %d\n", cap);
290        break;
291    }
292}
293
294void PlatformGraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
295{
296    size_t dashLength = dashes.size();
297    if (!dashLength)
298        return;
299
300    size_t count = !(dashLength % 2) ? dashLength : dashLength * 2;
301    SkScalar* intervals = new SkScalar[count];
302
303    for (unsigned int i = 0; i < count; i++)
304        intervals[i] = SkFloatToScalar(dashes[i % dashLength]);
305    SkPathEffect **effectPtr = &m_state->pathEffect;
306    SkSafeUnref(*effectPtr);
307    *effectPtr = new SkDashPathEffect(intervals, count, SkFloatToScalar(dashOffset));
308
309    delete[] intervals;
310}
311
312void PlatformGraphicsContext::setLineJoin(LineJoin join)
313{
314    switch (join) {
315    case MiterJoin:
316        m_state->lineJoin = SkPaint::kMiter_Join;
317        break;
318    case RoundJoin:
319        m_state->lineJoin = SkPaint::kRound_Join;
320        break;
321    case BevelJoin:
322        m_state->lineJoin = SkPaint::kBevel_Join;
323        break;
324    default:
325        ALOGD("PlatformGraphicsContextSkia::setLineJoin: unknown LineJoin %d\n", join);
326        break;
327    }
328}
329
330void PlatformGraphicsContext::setMiterLimit(float limit)
331{
332    m_state->miterLimit = limit;
333}
334
335void PlatformGraphicsContext::setShadow(int radius, int dx, int dy, SkColor c)
336{
337    m_state->setShadow(radius, dx, dy, c);
338}
339
340void PlatformGraphicsContext::setShouldAntialias(bool useAA)
341{
342    m_state->useAA = useAA;
343}
344
345bool PlatformGraphicsContext::setStrokeColor(const Color& c)
346{
347    bool dirty = false;
348    if (m_state->strokeColor != c.rgb()) {
349        m_state->strokeColor = c.rgb();
350        dirty = true;
351    }
352    return setStrokeShader(0) || dirty;
353}
354
355bool PlatformGraphicsContext::setStrokeShader(SkShader* strokeShader)
356{
357    bool dirty = false;
358    if (strokeShader && m_state->strokeColor != Color::black) {
359        m_state->strokeColor = Color::black;
360        dirty = true;
361    }
362
363    if (strokeShader != m_state->strokeShader) {
364        SkSafeUnref(m_state->strokeShader);
365        m_state->strokeShader = strokeShader;
366        SkSafeRef(m_state->strokeShader);
367        dirty = true;
368    }
369    return dirty;
370}
371
372void PlatformGraphicsContext::setStrokeStyle(StrokeStyle style)
373{
374    m_state->strokeStyle = style;
375}
376
377void PlatformGraphicsContext::setStrokeThickness(float f)
378{
379    m_state->strokeThickness = f;
380}
381
382//**************************************
383// Paint setup
384//**************************************
385
386void PlatformGraphicsContext::setupPaintCommon(SkPaint* paint) const
387{
388    paint->setAntiAlias(m_state->useAA);
389    paint->setDither(true);
390    paint->setXfermodeMode(m_state->mode);
391    if (SkColorGetA(m_state->shadow.color) > 0) {
392
393        // Currently, only GraphicsContexts associated with the
394        // HTMLCanvasElement have shadows ignore transforms set.  This
395        // allows us to distinguish between CSS and Canvas shadows which
396        // have different rendering specifications.
397        SkScalar dy = m_state->shadow.dy;
398        uint32_t flags = SkBlurDrawLooper::kNone_BlurFlag;
399        if (shadowsIgnoreTransforms()) {
400            dy = -dy;
401            flags |= SkBlurDrawLooper::kIgnoreTransform_BlurFlag;
402            flags |= SkBlurDrawLooper::kOverrideColor_BlurFlag;
403        }
404
405        SkDrawLooper* looper = new SkBlurDrawLooper(m_state->shadow.blur,
406                                                    m_state->shadow.dx,
407                                                    dy,
408                                                    m_state->shadow.color,
409                                                    flags);
410        paint->setLooper(looper)->unref();
411    }
412    paint->setFilterBitmap(true);
413}
414
415void PlatformGraphicsContext::setupPaintFill(SkPaint* paint) const
416{
417    this->setupPaintCommon(paint);
418    paint->setColor(m_state->applyAlpha(m_state->fillColor));
419    paint->setShader(m_state->fillShader);
420}
421
422bool PlatformGraphicsContext::setupPaintShadow(SkPaint* paint, SkPoint* offset) const
423{
424    return m_state->setupShadowPaint(paint, offset, shadowsIgnoreTransforms());
425}
426
427bool PlatformGraphicsContext::setupPaintStroke(SkPaint* paint, SkRect* rect,
428                                               bool isHLine)
429{
430    this->setupPaintCommon(paint);
431    paint->setColor(m_state->applyAlpha(m_state->strokeColor));
432    paint->setShader(m_state->strokeShader);
433
434    float width = m_state->strokeThickness;
435
436    // This allows dashing and dotting to work properly for hairline strokes
437    // FIXME: Should we only do this for dashed and dotted strokes?
438    if (!width)
439        width = 1;
440
441    paint->setStyle(SkPaint::kStroke_Style);
442    paint->setStrokeWidth(SkFloatToScalar(width));
443    paint->setStrokeCap(m_state->lineCap);
444    paint->setStrokeJoin(m_state->lineJoin);
445    paint->setStrokeMiter(SkFloatToScalar(m_state->miterLimit));
446
447    if (rect && (RoundToInt(width) & 1))
448        rect->inset(-SK_ScalarHalf, -SK_ScalarHalf);
449
450    SkPathEffect* pe = m_state->pathEffect;
451    if (pe) {
452        paint->setPathEffect(pe);
453        return false;
454    }
455    switch (m_state->strokeStyle) {
456    case NoStroke:
457    case SolidStroke:
458        width = 0;
459        break;
460    case DashedStroke:
461        width = m_state->dashRatio * width;
462        break;
463        // No break
464    case DottedStroke:
465        break;
466    }
467
468    if (width > 0) {
469        // Return true if we're basically a dotted dash of squares
470        bool justSqrs = RoundToInt(width) == RoundToInt(paint->getStrokeWidth());
471
472        if (justSqrs || !isHLine || !setBitmapDash(paint, width)) {
473#if 0
474            // this is slow enough that we just skip it for now
475            // see http://b/issue?id=4163023
476            SkScalar intervals[] = { width, width };
477            pe = new SkDashPathEffect(intervals, 2, 0);
478            paint->setPathEffect(pe)->unref();
479#endif
480        }
481        return justSqrs;
482    }
483    return false;
484}
485
486}   // WebCore
487