SkDistanceFieldGen.cpp revision bc3d92a7d84b56eb235d6c2d9b7de00625200713
1/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkDistanceFieldGen.h"
9#include "SkPoint.h"
10
11struct DFData {
12    float   fAlpha;      // alpha value of source texel
13    float   fDistSq;     // distance squared to nearest (so far) edge texel
14    SkPoint fDistVector; // distance vector to nearest (so far) edge texel
15};
16
17enum NeighborFlags {
18    kLeft_NeighborFlag        = 0x01,
19    kRight_NeighborFlag       = 0x02,
20    kTopLeft_NeighborFlag     = 0x04,
21    kTop_NeighborFlag         = 0x08,
22    kTopRight_NeighborFlag    = 0x10,
23    kBottomLeft_NeighborFlag  = 0x20,
24    kBottom_NeighborFlag      = 0x40,
25    kBottomRight_NeighborFlag = 0x80,
26    kAll_NeighborFlags        = 0xff,
27
28    kNeighborFlagCount        = 8
29};
30
31// We treat an "edge" as a place where we cross from a texel >= 128 to a texel < 128,
32// or vice versa. This means we just need to check if the MSBs are different.
33// 'neighborFlags' is used to limit the directions in which we test to avoid indexing
34// outside of the image
35static bool found_edge(const unsigned char* imagePtr, int width, int neighborFlags) {
36    // the order of these should match the neighbor flags above
37    const int kNum8ConnectedNeighbors = 8;
38    const int offsets[8] = {-1, 1, -width-1, -width, -width+1, width-1, width, width+1 };
39    SkASSERT(kNum8ConnectedNeighbors == kNeighborFlagCount);
40
41    // search for an edge
42    unsigned char currVal = *imagePtr >> 7;
43    for (int i = 0; i < kNum8ConnectedNeighbors; ++i) {
44        unsigned char checkVal;
45        if ((1 << i) & neighborFlags) {
46            const unsigned char* checkPtr = imagePtr + offsets[i];
47            checkVal = *checkPtr >> 7;
48        } else {
49            checkVal = 0;
50        }
51        SkASSERT(checkVal == 0 || checkVal == 1);
52        SkASSERT(currVal == 0 || currVal == 1);
53        if (checkVal != currVal) {
54            return true;
55        }
56    }
57
58    return false;
59}
60
61static void init_glyph_data(DFData* data, unsigned char* edges, const unsigned char* image,
62                            int dataWidth, int dataHeight,
63                            int imageWidth, int imageHeight,
64                            int pad) {
65    data += pad*dataWidth;
66    data += pad;
67    edges += (pad*dataWidth + pad);
68
69    for (int j = 0; j < imageHeight; ++j) {
70        for (int i = 0; i < imageWidth; ++i) {
71            if (255 == *image) {
72                data->fAlpha = 1.0f;
73            } else {
74                data->fAlpha = (*image)*0.00392156862f;  // 1/255
75            }
76            int checkMask = kAll_NeighborFlags;
77            if (i == 0) {
78                checkMask &= ~(kLeft_NeighborFlag|kTopLeft_NeighborFlag|kBottomLeft_NeighborFlag);
79            }
80            if (i == imageWidth-1) {
81                checkMask &= ~(kRight_NeighborFlag|kTopRight_NeighborFlag|kBottomRight_NeighborFlag);
82            }
83            if (j == 0) {
84                checkMask &= ~(kTopLeft_NeighborFlag|kTop_NeighborFlag|kTopRight_NeighborFlag);
85            }
86            if (j == imageHeight-1) {
87                checkMask &= ~(kBottomLeft_NeighborFlag|kBottom_NeighborFlag|kBottomRight_NeighborFlag);
88            }
89            if (found_edge(image, imageWidth, checkMask)) {
90                *edges = 255;  // using 255 makes for convenient debug rendering
91            }
92            ++data;
93            ++image;
94            ++edges;
95        }
96        data += 2*pad;
97        edges += 2*pad;
98    }
99}
100
101// from Gustavson (2011)
102// computes the distance to an edge given an edge normal vector and a pixel's alpha value
103// assumes that direction has been pre-normalized
104static float edge_distance(const SkPoint& direction, float alpha) {
105    float dx = direction.fX;
106    float dy = direction.fY;
107    float distance;
108    if (SkScalarNearlyZero(dx) || SkScalarNearlyZero(dy)) {
109        distance = 0.5f - alpha;
110    } else {
111        // this is easier if we treat the direction as being in the first octant
112        // (other octants are symmetrical)
113        dx = SkScalarAbs(dx);
114        dy = SkScalarAbs(dy);
115        if (dx < dy) {
116            SkTSwap(dx, dy);
117        }
118
119        // a1 = 0.5*dy/dx is the smaller fractional area chopped off by the edge
120        // to avoid the divide, we just consider the numerator
121        float a1num = 0.5f*dy;
122
123        // we now compute the approximate distance, depending where the alpha falls
124        // relative to the edge fractional area
125
126        // if 0 <= alpha < a1
127        if (alpha*dx < a1num) {
128            // TODO: find a way to do this without square roots?
129            distance = 0.5f*(dx + dy) - SkScalarSqrt(2.0f*dx*dy*alpha);
130        // if a1 <= alpha <= 1 - a1
131        } else if (alpha*dx < (dx - a1num)) {
132            distance = (0.5f - alpha)*dx;
133        // if 1 - a1 < alpha <= 1
134        } else {
135            // TODO: find a way to do this without square roots?
136            distance = -0.5f*(dx + dy) + SkScalarSqrt(2.0f*dx*dy*(1.0f - alpha));
137        }
138    }
139
140    return distance;
141}
142
143static void init_distances(DFData* data, unsigned char* edges, int width, int height) {
144    // skip one pixel border
145    DFData* currData = data;
146    DFData* prevData = data - width;
147    DFData* nextData = data + width;
148
149    for (int j = 0; j < height; ++j) {
150        for (int i = 0; i < width; ++i) {
151            if (*edges) {
152                // we should not be in the one-pixel outside band
153                SkASSERT(i > 0 && i < width-1 && j > 0 && j < height-1);
154                // gradient will point from low to high
155                // +y is down in this case
156                // i.e., if you're outside, gradient points towards edge
157                // if you're inside, gradient points away from edge
158                SkPoint currGrad;
159                currGrad.fX = (prevData+1)->fAlpha - (prevData-1)->fAlpha
160                             + SK_ScalarSqrt2*(currData+1)->fAlpha
161                             - SK_ScalarSqrt2*(currData-1)->fAlpha
162                             + (nextData+1)->fAlpha - (nextData-1)->fAlpha;
163                currGrad.fY = (nextData-1)->fAlpha - (prevData-1)->fAlpha
164                             + SK_ScalarSqrt2*nextData->fAlpha
165                             - SK_ScalarSqrt2*prevData->fAlpha
166                             + (nextData+1)->fAlpha - (prevData+1)->fAlpha;
167                currGrad.setLengthFast(1.0f);
168
169                // init squared distance to edge and distance vector
170                float dist = edge_distance(currGrad, currData->fAlpha);
171                currGrad.scale(dist, &currData->fDistVector);
172                currData->fDistSq = dist*dist;
173            } else {
174                // init distance to "far away"
175                currData->fDistSq = 2000000.f;
176                currData->fDistVector.fX = 1000.f;
177                currData->fDistVector.fY = 1000.f;
178            }
179            ++currData;
180            ++prevData;
181            ++nextData;
182            ++edges;
183        }
184    }
185}
186
187// Danielsson's 8SSEDT
188
189// first stage forward pass
190// (forward in Y, forward in X)
191static void F1(DFData* curr, int width) {
192    // upper left
193    DFData* check = curr - width-1;
194    SkPoint distVec = check->fDistVector;
195    float distSq = check->fDistSq - 2.0f*(distVec.fX + distVec.fY - 1.0f);
196    if (distSq < curr->fDistSq) {
197        distVec.fX -= 1.0f;
198        distVec.fY -= 1.0f;
199        curr->fDistSq = distSq;
200        curr->fDistVector = distVec;
201    }
202
203    // up
204    check = curr - width;
205    distVec = check->fDistVector;
206    distSq = check->fDistSq - 2.0f*distVec.fY + 1.0f;
207    if (distSq < curr->fDistSq) {
208        distVec.fY -= 1.0f;
209        curr->fDistSq = distSq;
210        curr->fDistVector = distVec;
211    }
212
213    // upper right
214    check = curr - width+1;
215    distVec = check->fDistVector;
216    distSq = check->fDistSq + 2.0f*(distVec.fX - distVec.fY + 1.0f);
217    if (distSq < curr->fDistSq) {
218        distVec.fX += 1.0f;
219        distVec.fY -= 1.0f;
220        curr->fDistSq = distSq;
221        curr->fDistVector = distVec;
222    }
223
224    // left
225    check = curr - 1;
226    distVec = check->fDistVector;
227    distSq = check->fDistSq - 2.0f*distVec.fX + 1.0f;
228    if (distSq < curr->fDistSq) {
229        distVec.fX -= 1.0f;
230        curr->fDistSq = distSq;
231        curr->fDistVector = distVec;
232    }
233}
234
235// second stage forward pass
236// (forward in Y, backward in X)
237static void F2(DFData* curr, int width) {
238    // right
239    DFData* check = curr + 1;
240    float distSq = check->fDistSq;
241    SkPoint distVec = check->fDistVector;
242    distSq = check->fDistSq + 2.0f*distVec.fX + 1.0f;
243    if (distSq < curr->fDistSq) {
244        distVec.fX += 1.0f;
245        curr->fDistSq = distSq;
246        curr->fDistVector = distVec;
247    }
248}
249
250// first stage backward pass
251// (backward in Y, forward in X)
252static void B1(DFData* curr, int width) {
253    // left
254    DFData* check = curr - 1;
255    SkPoint distVec = check->fDistVector;
256    float distSq = check->fDistSq - 2.0f*distVec.fX + 1.0f;
257    if (distSq < curr->fDistSq) {
258        distVec.fX -= 1.0f;
259        curr->fDistSq = distSq;
260        curr->fDistVector = distVec;
261    }
262}
263
264// second stage backward pass
265// (backward in Y, backwards in X)
266static void B2(DFData* curr, int width) {
267    // right
268    DFData* check = curr + 1;
269    SkPoint distVec = check->fDistVector;
270    float distSq = check->fDistSq + 2.0f*distVec.fX + 1.0f;
271    if (distSq < curr->fDistSq) {
272        distVec.fX += 1.0f;
273        curr->fDistSq = distSq;
274        curr->fDistVector = distVec;
275    }
276
277    // bottom left
278    check = curr + width-1;
279    distVec = check->fDistVector;
280    distSq = check->fDistSq - 2.0f*(distVec.fX - distVec.fY - 1.0f);
281    if (distSq < curr->fDistSq) {
282        distVec.fX -= 1.0f;
283        distVec.fY += 1.0f;
284        curr->fDistSq = distSq;
285        curr->fDistVector = distVec;
286    }
287
288    // bottom
289    check = curr + width;
290    distVec = check->fDistVector;
291    distSq = check->fDistSq + 2.0f*distVec.fY + 1.0f;
292    if (distSq < curr->fDistSq) {
293        distVec.fY += 1.0f;
294        curr->fDistSq = distSq;
295        curr->fDistVector = distVec;
296    }
297
298    // bottom right
299    check = curr + width+1;
300    distVec = check->fDistVector;
301    distSq = check->fDistSq + 2.0f*(distVec.fX + distVec.fY + 1.0f);
302    if (distSq < curr->fDistSq) {
303        distVec.fX += 1.0f;
304        distVec.fY += 1.0f;
305        curr->fDistSq = distSq;
306        curr->fDistVector = distVec;
307    }
308}
309
310// enable this to output edge data rather than the distance field
311#define DUMP_EDGE 0
312
313#if !DUMP_EDGE
314static unsigned char pack_distance_field_val(float dist, float distanceMagnitude) {
315    if (dist <= -distanceMagnitude) {
316        return 255;
317    } else if (dist > distanceMagnitude) {
318        return 0;
319    } else {
320        return (unsigned char)((distanceMagnitude-dist)*128.0f/distanceMagnitude);
321    }
322}
323#endif
324
325// assumes an 8-bit image and distance field
326bool SkGenerateDistanceFieldFromImage(unsigned char* distanceField,
327                                      const unsigned char* image,
328                                      int width, int height,
329                                      int distanceMagnitude) {
330    SkASSERT(NULL != distanceField);
331    SkASSERT(NULL != image);
332
333    // the final distance field will have additional texels on each side to handle
334    // the maximum distance
335    // we expand our temp data by one more on each side to simplify
336    // the scanning code -- will always be treated as infinitely far away
337    int pad = distanceMagnitude+1;
338
339    // set params for distance field data
340    int dataWidth = width + 2*pad;
341    int dataHeight = height + 2*pad;
342
343    // create temp data
344    size_t dataSize = dataWidth*dataHeight*sizeof(DFData);
345    SkAutoSMalloc<1024> dfStorage(dataSize);
346    DFData* dataPtr = (DFData*) dfStorage.get();
347    sk_bzero(dataPtr, dataSize);
348
349    SkAutoSMalloc<1024> edgeStorage(dataWidth*dataHeight*sizeof(char));
350    unsigned char* edgePtr = (unsigned char*) edgeStorage.get();
351    sk_bzero(edgePtr, dataWidth*dataHeight*sizeof(char));
352
353    // copy glyph into distance field storage
354    init_glyph_data(dataPtr, edgePtr, image,
355                    dataWidth, dataHeight,
356                    width, height, pad);
357
358    // create initial distance data, particularly at edges
359    init_distances(dataPtr, edgePtr, dataWidth, dataHeight);
360
361    // now perform Euclidean distance transform to propagate distances
362
363    // forwards in y
364    DFData* currData = dataPtr+dataWidth+1; // skip outer buffer
365    unsigned char* currEdge = edgePtr+dataWidth+1;
366    for (int j = 1; j < dataHeight-1; ++j) {
367        // forwards in x
368        for (int i = 1; i < dataWidth-1; ++i) {
369            // don't need to calculate distance for edge pixels
370            if (!*currEdge) {
371                F1(currData, dataWidth);
372            }
373            ++currData;
374            ++currEdge;
375        }
376
377        // backwards in x
378        --currData; // reset to end
379        --currEdge;
380        for (int i = 1; i < dataWidth-1; ++i) {
381            // don't need to calculate distance for edge pixels
382            if (!*currEdge) {
383                F2(currData, dataWidth);
384            }
385            --currData;
386            --currEdge;
387        }
388
389        currData += dataWidth+1;
390        currEdge += dataWidth+1;
391    }
392
393    // backwards in y
394    currData = dataPtr+dataWidth*(dataHeight-2) - 1; // skip outer buffer
395    currEdge = edgePtr+dataWidth*(dataHeight-2) - 1;
396    for (int j = 1; j < dataHeight-1; ++j) {
397        // forwards in x
398        for (int i = 1; i < dataWidth-1; ++i) {
399            // don't need to calculate distance for edge pixels
400            if (!*currEdge) {
401                B1(currData, dataWidth);
402            }
403            ++currData;
404            ++currEdge;
405        }
406
407        // backwards in x
408        --currData; // reset to end
409        --currEdge;
410        for (int i = 1; i < dataWidth-1; ++i) {
411            // don't need to calculate distance for edge pixels
412            if (!*currEdge) {
413                B2(currData, dataWidth);
414            }
415            --currData;
416            --currEdge;
417        }
418
419        currData -= dataWidth-1;
420        currEdge -= dataWidth-1;
421    }
422
423    // copy results to final distance field data
424    currData = dataPtr + dataWidth+1;
425    currEdge = edgePtr + dataWidth+1;
426    unsigned char *dfPtr = distanceField;
427    for (int j = 1; j < dataHeight-1; ++j) {
428        for (int i = 1; i < dataWidth-1; ++i) {
429#if DUMP_EDGE
430            unsigned char val = (currData->fAlpha >= 0.5f) ? 255 : 0;
431            if (*currEdge) {
432                val = 128;
433            }
434            *dfPtr++ = val;
435#else
436            float dist;
437            if (currData->fAlpha > 0.5f) {
438                dist = -SkScalarSqrt(currData->fDistSq);
439            } else {
440                dist = SkScalarSqrt(currData->fDistSq);
441            }
442            *dfPtr++ = pack_distance_field_val(dist, (float)distanceMagnitude);
443#endif
444            ++currData;
445            ++currEdge;
446        }
447        currData += 2;
448        currEdge += 2;
449    }
450
451    return true;
452}
453