SkScan_Hairline.cpp revision 4e05fd25c88bea64a988ededfc810770095ed97c
1
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkScan.h"
11#include "SkBlitter.h"
12#include "SkRasterClip.h"
13#include "SkFDot6.h"
14#include "SkLineClipper.h"
15
16static void horiline(int x, int stopx, SkFixed fy, SkFixed dy,
17                     SkBlitter* blitter) {
18    SkASSERT(x < stopx);
19
20    do {
21        blitter->blitH(x, fy >> 16, 1);
22        fy += dy;
23    } while (++x < stopx);
24}
25
26static void vertline(int y, int stopy, SkFixed fx, SkFixed dx,
27                     SkBlitter* blitter) {
28    SkASSERT(y < stopy);
29
30    do {
31        blitter->blitH(fx >> 16, y, 1);
32        fx += dx;
33    } while (++y < stopy);
34}
35
36#ifdef SK_DEBUG
37static bool canConvertFDot6ToFixed(SkFDot6 x) {
38    const int maxDot6 = SK_MaxS32 >> (16 - 6);
39    return SkAbs32(x) <= maxDot6;
40}
41#endif
42
43void SkScan::HairLineRgn(const SkPoint& pt0, const SkPoint& pt1,
44                         const SkRegion* clip, SkBlitter* blitter) {
45    SkBlitterClipper    clipper;
46    SkRect  r;
47    SkIRect clipR, ptsR;
48    SkPoint pts[2] = { pt0, pt1 };
49
50#ifdef SK_SCALAR_IS_FLOAT
51    // We have to pre-clip the line to fit in a SkFixed, so we just chop
52    // the line. TODO find a way to actually draw beyond that range.
53    {
54        SkRect fixedBounds;
55        const SkScalar max = SkIntToScalar(32767);
56        fixedBounds.set(-max, -max, max, max);
57        if (!SkLineClipper::IntersectLine(pts, fixedBounds, pts)) {
58            return;
59        }
60    }
61#endif
62
63    if (clip) {
64        // Perform a clip in scalar space, so we catch huge values which might
65        // be missed after we convert to SkFDot6 (overflow)
66        r.set(clip->getBounds());
67        if (!SkLineClipper::IntersectLine(pts, r, pts)) {
68            return;
69        }
70    }
71
72    SkFDot6 x0 = SkScalarToFDot6(pts[0].fX);
73    SkFDot6 y0 = SkScalarToFDot6(pts[0].fY);
74    SkFDot6 x1 = SkScalarToFDot6(pts[1].fX);
75    SkFDot6 y1 = SkScalarToFDot6(pts[1].fY);
76
77    SkASSERT(canConvertFDot6ToFixed(x0));
78    SkASSERT(canConvertFDot6ToFixed(y0));
79    SkASSERT(canConvertFDot6ToFixed(x1));
80    SkASSERT(canConvertFDot6ToFixed(y1));
81
82    if (clip) {
83        // now perform clipping again, as the rounding to dot6 can wiggle us
84        // our rects are really dot6 rects, but since we've already used
85        // lineclipper, we know they will fit in 32bits (26.6)
86        const SkIRect& bounds = clip->getBounds();
87
88        clipR.set(SkIntToFDot6(bounds.fLeft), SkIntToFDot6(bounds.fTop),
89                  SkIntToFDot6(bounds.fRight), SkIntToFDot6(bounds.fBottom));
90        ptsR.set(x0, y0, x1, y1);
91        ptsR.sort();
92
93        // outset the right and bottom, to account for how hairlines are
94        // actually drawn, which may hit the pixel to the right or below of
95        // the coordinate
96        ptsR.fRight += SK_FDot6One;
97        ptsR.fBottom += SK_FDot6One;
98
99        if (!SkIRect::Intersects(ptsR, clipR)) {
100            return;
101        }
102        if (clip->isRect() && clipR.contains(ptsR)) {
103            clip = NULL;
104        } else {
105            blitter = clipper.apply(blitter, clip);
106        }
107    }
108
109    SkFDot6 dx = x1 - x0;
110    SkFDot6 dy = y1 - y0;
111
112    if (SkAbs32(dx) > SkAbs32(dy)) { // mostly horizontal
113        if (x0 > x1) {   // we want to go left-to-right
114            SkTSwap<SkFDot6>(x0, x1);
115            SkTSwap<SkFDot6>(y0, y1);
116        }
117        int ix0 = SkFDot6Round(x0);
118        int ix1 = SkFDot6Round(x1);
119        if (ix0 == ix1) {// too short to draw
120            return;
121        }
122
123        SkFixed slope = SkFixedDiv(dy, dx);
124        SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6);
125
126        horiline(ix0, ix1, startY, slope, blitter);
127    } else {              // mostly vertical
128        if (y0 > y1) {   // we want to go top-to-bottom
129            SkTSwap<SkFDot6>(x0, x1);
130            SkTSwap<SkFDot6>(y0, y1);
131        }
132        int iy0 = SkFDot6Round(y0);
133        int iy1 = SkFDot6Round(y1);
134        if (iy0 == iy1) { // too short to draw
135            return;
136        }
137
138        SkFixed slope = SkFixedDiv(dx, dy);
139        SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6);
140
141        vertline(iy0, iy1, startX, slope, blitter);
142    }
143}
144
145// we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right
146// and double-hit the top-left.
147// TODO: handle huge coordinates on rect (before calling SkScalarToFixed)
148void SkScan::HairRect(const SkRect& rect, const SkRasterClip& clip,
149                      SkBlitter* blitter) {
150    SkAAClipBlitterWrapper wrapper;
151    SkBlitterClipper    clipper;
152    SkIRect             r;
153
154    r.set(SkScalarToFixed(rect.fLeft) >> 16,
155          SkScalarToFixed(rect.fTop) >> 16,
156          (SkScalarToFixed(rect.fRight) >> 16) + 1,
157          (SkScalarToFixed(rect.fBottom) >> 16) + 1);
158
159    if (clip.quickReject(r)) {
160        return;
161    }
162    if (!clip.quickContains(r)) {
163        const SkRegion* clipRgn;
164        if (clip.isBW()) {
165            clipRgn = &clip.bwRgn();
166        } else {
167            wrapper.init(clip, blitter);
168            clipRgn = &wrapper.getRgn();
169            blitter = wrapper.getBlitter();
170        }
171        blitter = clipper.apply(blitter, clipRgn);
172    }
173
174    int width = r.width();
175    int height = r.height();
176
177    if ((width | height) == 0) {
178        return;
179    }
180    if (width <= 2 || height <= 2) {
181        blitter->blitRect(r.fLeft, r.fTop, width, height);
182        return;
183    }
184    // if we get here, we know we have 4 segments to draw
185    blitter->blitH(r.fLeft, r.fTop, width);                     // top
186    blitter->blitRect(r.fLeft, r.fTop + 1, 1, height - 2);      // left
187    blitter->blitRect(r.fRight - 1, r.fTop + 1, 1, height - 2); // right
188    blitter->blitH(r.fLeft, r.fBottom - 1, width);              // bottom
189}
190
191///////////////////////////////////////////////////////////////////////////////
192
193#include "SkPath.h"
194#include "SkGeometry.h"
195
196static int compute_int_quad_dist(const SkPoint pts[3]) {
197    // compute the vector between the control point ([1]) and the middle of the
198    // line connecting the start and end ([0] and [2])
199    SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX;
200    SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY;
201    // we want everyone to be positive
202    dx = SkScalarAbs(dx);
203    dy = SkScalarAbs(dy);
204    // convert to whole pixel values (use ceiling to be conservative)
205    int idx = SkScalarCeil(dx);
206    int idy = SkScalarCeil(dy);
207    // use the cheap approx for distance
208    if (idx > idy) {
209        return idx + (idy >> 1);
210    } else {
211        return idy + (idx >> 1);
212    }
213}
214
215typedef void (*LineProc)(const SkPoint&, const SkPoint&, const SkRegion*,
216                         SkBlitter*);
217
218static void hairquad(const SkPoint pts[3], const SkRegion* clip,
219                     SkBlitter* blitter, int level, LineProc lineproc) {
220    if (level > 0) {
221        SkPoint tmp[5];
222
223        SkChopQuadAtHalf(pts, tmp);
224        hairquad(tmp, clip, blitter, level - 1, lineproc);
225        hairquad(&tmp[2], clip, blitter, level - 1, lineproc);
226    } else {
227        lineproc(pts[0], pts[2], clip, blitter);
228    }
229}
230
231static void haircubic(const SkPoint pts[4], const SkRegion* clip,
232                      SkBlitter* blitter, int level, LineProc lineproc) {
233    if (level > 0) {
234        SkPoint tmp[7];
235
236        SkChopCubicAt(pts, tmp, SK_Scalar1/2);
237        haircubic(tmp, clip, blitter, level - 1, lineproc);
238        haircubic(&tmp[3], clip, blitter, level - 1, lineproc);
239    } else {
240        lineproc(pts[0], pts[3], clip, blitter);
241    }
242}
243
244#define kMaxCubicSubdivideLevel 6
245#define kMaxQuadSubdivideLevel  5
246
247static int compute_quad_level(const SkPoint pts[3]) {
248    int d = compute_int_quad_dist(pts);
249    /*  quadratics approach the line connecting their start and end points
250     4x closer with each subdivision, so we compute the number of
251     subdivisions to be the minimum need to get that distance to be less
252     than a pixel.
253     */
254    int level = (33 - SkCLZ(d)) >> 1;
255    // sanity check on level (from the previous version)
256    if (level > kMaxQuadSubdivideLevel) {
257        level = kMaxQuadSubdivideLevel;
258    }
259    return level;
260}
261
262static void hair_path(const SkPath& path, const SkRasterClip& rclip,
263                      SkBlitter* blitter, LineProc lineproc) {
264    if (path.isEmpty()) {
265        return;
266    }
267
268    SkAAClipBlitterWrapper wrap;
269    const SkRegion* clip = NULL;
270
271    {
272        SkIRect ibounds;
273        path.getBounds().roundOut(&ibounds);
274        ibounds.inset(-1, -1);
275
276        if (rclip.quickReject(ibounds)) {
277            return;
278        }
279        if (!rclip.quickContains(ibounds)) {
280            if (rclip.isBW()) {
281                clip = &rclip.bwRgn();
282            } else {
283                wrap.init(rclip, blitter);
284                blitter = wrap.getBlitter();
285                clip = &wrap.getRgn();
286            }
287        }
288    }
289
290    SkPath::Iter    iter(path, false);
291    SkPoint         pts[4];
292    SkPath::Verb    verb;
293    SkAutoConicToQuads converter;
294
295    while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
296        switch (verb) {
297            case SkPath::kMove_Verb:
298                break;
299            case SkPath::kLine_Verb:
300                lineproc(pts[0], pts[1], clip, blitter);
301                break;
302            case SkPath::kQuad_Verb:
303                hairquad(pts, clip, blitter, compute_quad_level(pts), lineproc);
304                break;
305            case SkPath::kConic_Verb: {
306                // how close should the quads be to the original conic?
307                const SkScalar tol = SK_Scalar1 / 4;
308                const SkPoint* quadPts = converter.computeQuads(pts,
309                                                       iter.conicWeight(), tol);
310                for (int i = 0; i < converter.countQuads(); ++i) {
311                    int level = compute_quad_level(quadPts);
312                    hairquad(quadPts, clip, blitter, level, lineproc);
313                    quadPts += 2;
314                }
315                break;
316            }
317            case SkPath::kCubic_Verb:
318                haircubic(pts, clip, blitter, kMaxCubicSubdivideLevel, lineproc);
319                break;
320            case SkPath::kClose_Verb:
321                break;
322            case SkPath::kDone_Verb:
323                break;
324        }
325    }
326}
327
328void SkScan::HairPath(const SkPath& path, const SkRasterClip& clip,
329                      SkBlitter* blitter) {
330    hair_path(path, clip, blitter, SkScan::HairLineRgn);
331}
332
333void SkScan::AntiHairPath(const SkPath& path, const SkRasterClip& clip,
334                          SkBlitter* blitter) {
335    hair_path(path, clip, blitter, SkScan::AntiHairLineRgn);
336}
337
338///////////////////////////////////////////////////////////////////////////////
339
340void SkScan::FrameRect(const SkRect& r, const SkPoint& strokeSize,
341                       const SkRasterClip& clip, SkBlitter* blitter) {
342    SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0);
343
344    if (strokeSize.fX < 0 || strokeSize.fY < 0) {
345        return;
346    }
347
348    const SkScalar dx = strokeSize.fX;
349    const SkScalar dy = strokeSize.fY;
350    SkScalar rx = SkScalarHalf(dx);
351    SkScalar ry = SkScalarHalf(dy);
352    SkRect   outer, tmp;
353
354    outer.set(r.fLeft - rx, r.fTop - ry,
355                r.fRight + rx, r.fBottom + ry);
356
357    if (r.width() <= dx || r.height() <= dx) {
358        SkScan::FillRect(outer, clip, blitter);
359        return;
360    }
361
362    tmp.set(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + dy);
363    SkScan::FillRect(tmp, clip, blitter);
364    tmp.fTop = outer.fBottom - dy;
365    tmp.fBottom = outer.fBottom;
366    SkScan::FillRect(tmp, clip, blitter);
367
368    tmp.set(outer.fLeft, outer.fTop + dy, outer.fLeft + dx, outer.fBottom - dy);
369    SkScan::FillRect(tmp, clip, blitter);
370    tmp.fLeft = outer.fRight - dx;
371    tmp.fRight = outer.fRight;
372    SkScan::FillRect(tmp, clip, blitter);
373}
374
375void SkScan::HairLine(const SkPoint& p0, const SkPoint& p1,
376                      const SkRasterClip& clip, SkBlitter* blitter) {
377    if (clip.isBW()) {
378        HairLineRgn(p0, p1, &clip.bwRgn(), blitter);
379    } else {
380        const SkRegion* clipRgn = NULL;
381        SkRect r;
382        SkIRect ir;
383        r.set(p0.fX, p0.fY, p1.fX, p1.fY);
384        r.sort();
385        r.inset(-SK_ScalarHalf, -SK_ScalarHalf);
386        r.roundOut(&ir);
387
388        SkAAClipBlitterWrapper wrap;
389        if (!clip.quickContains(ir)) {
390            wrap.init(clip, blitter);
391            blitter = wrap.getBlitter();
392            clipRgn = &wrap.getRgn();
393        }
394        HairLineRgn(p0, p1, clipRgn, blitter);
395    }
396}
397
398void SkScan::AntiHairLine(const SkPoint& p0, const SkPoint& p1,
399                          const SkRasterClip& clip, SkBlitter* blitter) {
400    if (clip.isBW()) {
401        AntiHairLineRgn(p0, p1, &clip.bwRgn(), blitter);
402    } else {
403        const SkRegion* clipRgn = NULL;
404        SkRect r;
405        SkIRect ir;
406        r.set(p0.fX, p0.fY, p1.fX, p1.fY);
407        r.sort();
408        r.roundOut(&ir);
409        ir.inset(-1, -1);
410
411        SkAAClipBlitterWrapper wrap;
412        if (!clip.quickContains(ir)) {
413            wrap.init(clip, blitter);
414            blitter = wrap.getBlitter();
415            clipRgn = &wrap.getRgn();
416        }
417        AntiHairLineRgn(p0, p1, clipRgn, blitter);
418    }
419}
420