SkScan_Hairline.cpp revision 8a1c16ff38322f0210116fa7293eb8817c7e477e
1/* libs/graphics/sgl/SkScan_Hairline.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18#include "SkScan.h"
19#include "SkBlitter.h"
20#include "SkRegion.h"
21#include "SkFDot6.h"
22
23static void horiline(int x, int stopx, SkFixed fy, SkFixed dy, SkBlitter* blitter)
24{
25    SkASSERT(x < stopx);
26
27    do {
28        blitter->blitH(x, fy >> 16, 1);
29        fy += dy;
30    } while (++x < stopx);
31}
32
33static void vertline(int y, int stopy, SkFixed fx, SkFixed dx, SkBlitter* blitter)
34{
35    SkASSERT(y < stopy);
36
37    do {
38        blitter->blitH(fx >> 16, y, 1);
39        fx += dx;
40    } while (++y < stopy);
41}
42
43void SkScan::HairLine(const SkPoint& pt0, const SkPoint& pt1, const SkRegion* clip, SkBlitter* blitter)
44{
45    SkBlitterClipper    clipper;
46
47    SkFDot6 x0 = SkScalarToFDot6(pt0.fX);
48    SkFDot6 y0 = SkScalarToFDot6(pt0.fY);
49    SkFDot6 x1 = SkScalarToFDot6(pt1.fX);
50    SkFDot6 y1 = SkScalarToFDot6(pt1.fY);
51
52    if (clip)
53    {
54        SkRect      r;
55        SkIRect     ir;
56        SkPoint     pts[2];
57
58        pts[0] = pt0;
59        pts[1] = pt1;
60        r.set(pts, 2);
61        r.roundOut(&ir);
62
63        // if we're given a horizontal or vertical line
64        // this rect could be empty (in area), in which case
65        // clip->quickReject() will always return true.
66        // hence we bloat the rect to avoid that case
67        if (ir.width() == 0)
68            ir.fRight += 1;
69        if (ir.height() == 0)
70            ir.fBottom += 1;
71
72        if (clip->quickReject(ir))
73            return;
74        if (clip->quickContains(ir))
75            clip = NULL;
76        else
77        {
78            blitter = clipper.apply(blitter, clip);
79        }
80    }
81
82    SkFDot6 dx = x1 - x0;
83    SkFDot6 dy = y1 - y0;
84
85    if (SkAbs32(dx) > SkAbs32(dy))  // mostly horizontal
86    {
87        if (x0 > x1)    // we want to go left-to-right
88        {
89            SkTSwap<SkFDot6>(x0, x1);
90            SkTSwap<SkFDot6>(y0, y1);
91        }
92        int ix0 = SkFDot6Round(x0);
93        int ix1 = SkFDot6Round(x1);
94        if (ix0 == ix1) // too short to draw
95            return;
96
97        SkFixed slope = SkFixedDiv(dy, dx);
98        SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6);
99
100        horiline(ix0, ix1, startY, slope, blitter);
101    }
102    else                // mostly vertical
103    {
104        if (y0 > y1)    // we want to go top-to-bottom
105        {
106            SkTSwap<SkFDot6>(x0, x1);
107            SkTSwap<SkFDot6>(y0, y1);
108        }
109        int iy0 = SkFDot6Round(y0);
110        int iy1 = SkFDot6Round(y1);
111        if (iy0 == iy1) // too short to draw
112            return;
113
114        SkFixed slope = SkFixedDiv(dx, dy);
115        SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6);
116
117        vertline(iy0, iy1, startX, slope, blitter);
118    }
119}
120
121// we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right
122// and double-hit the top-left.
123void SkScan::HairRect(const SkRect& rect, const SkRegion* clip, SkBlitter* blitter)
124{
125    SkBlitterClipper    clipper;
126    SkIRect             r;
127
128    r.set(SkScalarToFixed(rect.fLeft) >> 16,
129          SkScalarToFixed(rect.fTop) >> 16,
130          (SkScalarToFixed(rect.fRight) >> 16) + 1,
131          (SkScalarToFixed(rect.fBottom) >> 16) + 1);
132
133    if (clip)
134    {
135        if (clip->quickReject(r))
136            return;
137        if (!clip->quickContains(r))
138            blitter = clipper.apply(blitter, clip);
139    }
140
141    int width = r.width();
142    int height = r.height();
143
144    if ((width | height) == 0)
145        return;
146    if (width <= 2 || height <= 2)
147    {
148        blitter->blitRect(r.fLeft, r.fTop, width, height);
149        return;
150    }
151    // if we get here, we know we have 4 segments to draw
152    blitter->blitH(r.fLeft, r.fTop, width);                     // top
153    blitter->blitRect(r.fLeft, r.fTop + 1, 1, height - 2);      // left
154    blitter->blitRect(r.fRight - 1, r.fTop + 1, 1, height - 2); // right
155    blitter->blitH(r.fLeft, r.fBottom - 1, width);              // bottom
156}
157
158/////////////////////////////////////////////////////////////////////////////////////////////////
159
160#include "SkPath.h"
161#include "SkGeometry.h"
162
163static bool quad_too_curvy(const SkPoint pts[3])
164{
165    return true;
166}
167
168static int compute_int_quad_dist(const SkPoint pts[3]) {
169    // compute the vector between the control point ([1]) and the middle of the
170    // line connecting the start and end ([0] and [2])
171    SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX;
172    SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY;
173    // we want everyone to be positive
174    dx = SkScalarAbs(dx);
175    dy = SkScalarAbs(dy);
176    // convert to whole pixel values (use ceiling to be conservative)
177    int idx = SkScalarCeil(dx);
178    int idy = SkScalarCeil(dy);
179    // use the cheap approx for distance
180    if (idx > idy) {
181        return idx + (idy >> 1);
182    } else {
183        return idy + (idx >> 1);
184    }
185}
186
187static void hairquad(const SkPoint pts[3], const SkRegion* clip, SkBlitter* blitter, int level,
188                     void (*lineproc)(const SkPoint&, const SkPoint&, const SkRegion* clip, SkBlitter*))
189{
190#if 1
191    if (level > 0 && quad_too_curvy(pts))
192    {
193        SkPoint tmp[5];
194
195        SkChopQuadAtHalf(pts, tmp);
196        hairquad(tmp, clip, blitter, level - 1, lineproc);
197        hairquad(&tmp[2], clip, blitter, level - 1, lineproc);
198    }
199    else
200        lineproc(pts[0], pts[2], clip, blitter);
201#else
202    int count = 1 << level;
203    const SkScalar dt = SkFixedToScalar(SK_Fixed1 >> level);
204    SkScalar t = dt;
205    SkPoint prevPt = pts[0];
206    for (int i = 1; i < count; i++) {
207        SkPoint nextPt;
208        SkEvalQuadAt(pts, t, &nextPt);
209        lineproc(prevPt, nextPt, clip, blitter);
210        t += dt;
211        prevPt = nextPt;
212    }
213    // draw the last line explicitly to 1.0, in case t didn't match that exactly
214    lineproc(prevPt, pts[2], clip, blitter);
215#endif
216}
217
218static bool cubic_too_curvy(const SkPoint pts[4])
219{
220    return true;
221}
222
223static void haircubic(const SkPoint pts[4], const SkRegion* clip, SkBlitter* blitter, int level,
224                      void (*lineproc)(const SkPoint&, const SkPoint&, const SkRegion*, SkBlitter*))
225{
226    if (level > 0 && cubic_too_curvy(pts))
227    {
228        SkPoint tmp[7];
229
230        SkChopCubicAt(pts, tmp, SK_Scalar1/2);
231        haircubic(tmp, clip, blitter, level - 1, lineproc);
232        haircubic(&tmp[3], clip, blitter, level - 1, lineproc);
233    }
234    else
235        lineproc(pts[0], pts[3], clip, blitter);
236}
237
238#define kMaxCubicSubdivideLevel 6
239#define kMaxQuadSubdivideLevel  5
240
241static void hair_path(const SkPath& path, const SkRegion* clip, SkBlitter* blitter,
242                      void (*lineproc)(const SkPoint&, const SkPoint&, const SkRegion*, SkBlitter*))
243{
244    if (path.isEmpty())
245        return;
246
247    const SkIRect* clipR = NULL;
248
249    if (clip)
250    {
251        SkRect      bounds;
252        SkIRect     ibounds;
253
254        path.computeBounds(&bounds, SkPath::kFast_BoundsType);
255        bounds.roundOut(&ibounds);
256        ibounds.inset(-1, -1);
257
258        if (clip->quickReject(ibounds))
259            return;
260
261        if (clip->quickContains(ibounds))
262            clip = NULL;
263        else
264            clipR = &clip->getBounds();
265    }
266
267    SkPath::Iter    iter(path, false);
268    SkPoint         pts[4];
269    SkPath::Verb    verb;
270
271    while ((verb = iter.next(pts)) != SkPath::kDone_Verb)
272    {
273        switch (verb) {
274        case SkPath::kLine_Verb:
275            lineproc(pts[0], pts[1], clip, blitter);
276            break;
277        case SkPath::kQuad_Verb: {
278            int d = compute_int_quad_dist(pts);
279            /*  quadratics approach the line connecting their start and end points
280             4x closer with each subdivision, so we compute the number of
281             subdivisions to be the minimum need to get that distance to be less
282             than a pixel.
283             */
284            int level = (33 - SkCLZ(d)) >> 1;
285//          SkDebugf("----- distance %d computedLevel %d\n", d, computedLevel);
286            // sanity check on level (from the previous version)
287            if (level > kMaxQuadSubdivideLevel) {
288                level = kMaxQuadSubdivideLevel;
289            }
290            hairquad(pts, clip, blitter, level, lineproc);
291            break;
292        }
293        case SkPath::kCubic_Verb:
294            haircubic(pts, clip, blitter, kMaxCubicSubdivideLevel, lineproc);
295            break;
296        default:
297            break;
298        }
299    }
300}
301
302void SkScan::HairPath(const SkPath& path, const SkRegion* clip, SkBlitter* blitter)
303{
304    hair_path(path, clip, blitter, SkScan::HairLine);
305}
306
307void SkScan::AntiHairPath(const SkPath& path, const SkRegion* clip, SkBlitter* blitter)
308{
309    hair_path(path, clip, blitter, SkScan::AntiHairLine);
310}
311
312////////////////////////////////////////////////////////////////////////////////
313
314void SkScan::FrameRect(const SkRect& r, SkScalar diameter, const SkRegion* clip, SkBlitter* blitter)
315{
316    SkASSERT(diameter > 0);
317
318    if (r.isEmpty())
319        return;
320
321    SkScalar radius = diameter / 2;
322    SkRect   outer, tmp;
323
324    outer.set(  r.fLeft - radius, r.fTop - radius,
325                r.fRight + radius, r.fBottom + radius);
326
327    if (r.width() <= diameter || r.height() <= diameter)
328    {
329        SkScan::FillRect(outer, clip, blitter);
330        return;
331    }
332
333    tmp.set(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + diameter);
334    SkScan::FillRect(tmp, clip, blitter);
335    tmp.fTop = outer.fBottom - diameter;
336    tmp.fBottom = outer.fBottom;
337    SkScan::FillRect(tmp, clip, blitter);
338
339    tmp.set(outer.fLeft, outer.fTop + diameter, outer.fLeft + diameter, outer.fBottom - diameter);
340    SkScan::FillRect(tmp, clip, blitter);
341    tmp.fLeft = outer.fRight - diameter;
342    tmp.fRight = outer.fRight;
343    SkScan::FillRect(tmp, clip, blitter);
344}
345
346