• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
11 struct 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 
17 enum 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 >=128 to <128, or vice versa, or
32 // where we have two non-zero pixels that are <128.
33 // 'neighborFlags' is used to limit the directions in which we test to avoid indexing
34 // outside of the image
found_edge(const unsigned char * imagePtr,int width,int neighborFlags)35 static 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;
43     unsigned char currCheck = (currVal >> 7);
44     for (int i = 0; i < kNum8ConnectedNeighbors; ++i) {
45         unsigned char neighborVal;
46         if ((1 << i) & neighborFlags) {
47             const unsigned char* checkPtr = imagePtr + offsets[i];
48             neighborVal = *checkPtr;
49         } else {
50             neighborVal = 0;
51         }
52         unsigned char neighborCheck = (neighborVal >> 7);
53         SkASSERT(currCheck == 0 || currCheck == 1);
54         SkASSERT(neighborCheck == 0 || neighborCheck == 1);
55         // if sharp transition
56         if (currCheck != neighborCheck ||
57             // or both <128 and >0
58             (!currCheck && !neighborCheck && currVal && neighborVal)) {
59             return true;
60         }
61     }
62 
63     return false;
64 }
65 
init_glyph_data(DFData * data,unsigned char * edges,const unsigned char * image,int dataWidth,int dataHeight,int imageWidth,int imageHeight,int pad)66 static void init_glyph_data(DFData* data, unsigned char* edges, const unsigned char* image,
67                             int dataWidth, int dataHeight,
68                             int imageWidth, int imageHeight,
69                             int pad) {
70     data += pad*dataWidth;
71     data += pad;
72     edges += (pad*dataWidth + pad);
73 
74     for (int j = 0; j < imageHeight; ++j) {
75         for (int i = 0; i < imageWidth; ++i) {
76             if (255 == *image) {
77                 data->fAlpha = 1.0f;
78             } else {
79                 data->fAlpha = (*image)*0.00392156862f;  // 1/255
80             }
81             int checkMask = kAll_NeighborFlags;
82             if (i == 0) {
83                 checkMask &= ~(kLeft_NeighborFlag|kTopLeft_NeighborFlag|kBottomLeft_NeighborFlag);
84             }
85             if (i == imageWidth-1) {
86                 checkMask &= ~(kRight_NeighborFlag|kTopRight_NeighborFlag|kBottomRight_NeighborFlag);
87             }
88             if (j == 0) {
89                 checkMask &= ~(kTopLeft_NeighborFlag|kTop_NeighborFlag|kTopRight_NeighborFlag);
90             }
91             if (j == imageHeight-1) {
92                 checkMask &= ~(kBottomLeft_NeighborFlag|kBottom_NeighborFlag|kBottomRight_NeighborFlag);
93             }
94             if (found_edge(image, imageWidth, checkMask)) {
95                 *edges = 255;  // using 255 makes for convenient debug rendering
96             }
97             ++data;
98             ++image;
99             ++edges;
100         }
101         data += 2*pad;
102         edges += 2*pad;
103     }
104 }
105 
106 // from Gustavson (2011)
107 // computes the distance to an edge given an edge normal vector and a pixel's alpha value
108 // assumes that direction has been pre-normalized
edge_distance(const SkPoint & direction,float alpha)109 static float edge_distance(const SkPoint& direction, float alpha) {
110     float dx = direction.fX;
111     float dy = direction.fY;
112     float distance;
113     if (SkScalarNearlyZero(dx) || SkScalarNearlyZero(dy)) {
114         distance = 0.5f - alpha;
115     } else {
116         // this is easier if we treat the direction as being in the first octant
117         // (other octants are symmetrical)
118         dx = SkScalarAbs(dx);
119         dy = SkScalarAbs(dy);
120         if (dx < dy) {
121             SkTSwap(dx, dy);
122         }
123 
124         // a1 = 0.5*dy/dx is the smaller fractional area chopped off by the edge
125         // to avoid the divide, we just consider the numerator
126         float a1num = 0.5f*dy;
127 
128         // we now compute the approximate distance, depending where the alpha falls
129         // relative to the edge fractional area
130 
131         // if 0 <= alpha < a1
132         if (alpha*dx < a1num) {
133             // TODO: find a way to do this without square roots?
134             distance = 0.5f*(dx + dy) - SkScalarSqrt(2.0f*dx*dy*alpha);
135         // if a1 <= alpha <= 1 - a1
136         } else if (alpha*dx < (dx - a1num)) {
137             distance = (0.5f - alpha)*dx;
138         // if 1 - a1 < alpha <= 1
139         } else {
140             // TODO: find a way to do this without square roots?
141             distance = -0.5f*(dx + dy) + SkScalarSqrt(2.0f*dx*dy*(1.0f - alpha));
142         }
143     }
144 
145     return distance;
146 }
147 
init_distances(DFData * data,unsigned char * edges,int width,int height)148 static void init_distances(DFData* data, unsigned char* edges, int width, int height) {
149     // skip one pixel border
150     DFData* currData = data;
151     DFData* prevData = data - width;
152     DFData* nextData = data + width;
153 
154     for (int j = 0; j < height; ++j) {
155         for (int i = 0; i < width; ++i) {
156             if (*edges) {
157                 // we should not be in the one-pixel outside band
158                 SkASSERT(i > 0 && i < width-1 && j > 0 && j < height-1);
159                 // gradient will point from low to high
160                 // +y is down in this case
161                 // i.e., if you're outside, gradient points towards edge
162                 // if you're inside, gradient points away from edge
163                 SkPoint currGrad;
164                 currGrad.fX = (prevData+1)->fAlpha - (prevData-1)->fAlpha
165                              + SK_ScalarSqrt2*(currData+1)->fAlpha
166                              - SK_ScalarSqrt2*(currData-1)->fAlpha
167                              + (nextData+1)->fAlpha - (nextData-1)->fAlpha;
168                 currGrad.fY = (nextData-1)->fAlpha - (prevData-1)->fAlpha
169                              + SK_ScalarSqrt2*nextData->fAlpha
170                              - SK_ScalarSqrt2*prevData->fAlpha
171                              + (nextData+1)->fAlpha - (prevData+1)->fAlpha;
172                 currGrad.setLengthFast(1.0f);
173 
174                 // init squared distance to edge and distance vector
175                 float dist = edge_distance(currGrad, currData->fAlpha);
176                 currGrad.scale(dist, &currData->fDistVector);
177                 currData->fDistSq = dist*dist;
178             } else {
179                 // init distance to "far away"
180                 currData->fDistSq = 2000000.f;
181                 currData->fDistVector.fX = 1000.f;
182                 currData->fDistVector.fY = 1000.f;
183             }
184             ++currData;
185             ++prevData;
186             ++nextData;
187             ++edges;
188         }
189     }
190 }
191 
192 // Danielsson's 8SSEDT
193 
194 // first stage forward pass
195 // (forward in Y, forward in X)
F1(DFData * curr,int width)196 static void F1(DFData* curr, int width) {
197     // upper left
198     DFData* check = curr - width-1;
199     SkPoint distVec = check->fDistVector;
200     float distSq = check->fDistSq - 2.0f*(distVec.fX + distVec.fY - 1.0f);
201     if (distSq < curr->fDistSq) {
202         distVec.fX -= 1.0f;
203         distVec.fY -= 1.0f;
204         curr->fDistSq = distSq;
205         curr->fDistVector = distVec;
206     }
207 
208     // up
209     check = curr - width;
210     distVec = check->fDistVector;
211     distSq = check->fDistSq - 2.0f*distVec.fY + 1.0f;
212     if (distSq < curr->fDistSq) {
213         distVec.fY -= 1.0f;
214         curr->fDistSq = distSq;
215         curr->fDistVector = distVec;
216     }
217 
218     // upper right
219     check = curr - width+1;
220     distVec = check->fDistVector;
221     distSq = check->fDistSq + 2.0f*(distVec.fX - distVec.fY + 1.0f);
222     if (distSq < curr->fDistSq) {
223         distVec.fX += 1.0f;
224         distVec.fY -= 1.0f;
225         curr->fDistSq = distSq;
226         curr->fDistVector = distVec;
227     }
228 
229     // left
230     check = curr - 1;
231     distVec = check->fDistVector;
232     distSq = check->fDistSq - 2.0f*distVec.fX + 1.0f;
233     if (distSq < curr->fDistSq) {
234         distVec.fX -= 1.0f;
235         curr->fDistSq = distSq;
236         curr->fDistVector = distVec;
237     }
238 }
239 
240 // second stage forward pass
241 // (forward in Y, backward in X)
F2(DFData * curr,int width)242 static void F2(DFData* curr, int width) {
243     // right
244     DFData* check = curr + 1;
245     float distSq = check->fDistSq;
246     SkPoint distVec = check->fDistVector;
247     distSq = check->fDistSq + 2.0f*distVec.fX + 1.0f;
248     if (distSq < curr->fDistSq) {
249         distVec.fX += 1.0f;
250         curr->fDistSq = distSq;
251         curr->fDistVector = distVec;
252     }
253 }
254 
255 // first stage backward pass
256 // (backward in Y, forward in X)
B1(DFData * curr,int width)257 static void B1(DFData* curr, int width) {
258     // left
259     DFData* check = curr - 1;
260     SkPoint distVec = check->fDistVector;
261     float distSq = check->fDistSq - 2.0f*distVec.fX + 1.0f;
262     if (distSq < curr->fDistSq) {
263         distVec.fX -= 1.0f;
264         curr->fDistSq = distSq;
265         curr->fDistVector = distVec;
266     }
267 }
268 
269 // second stage backward pass
270 // (backward in Y, backwards in X)
B2(DFData * curr,int width)271 static void B2(DFData* curr, int width) {
272     // right
273     DFData* check = curr + 1;
274     SkPoint distVec = check->fDistVector;
275     float distSq = check->fDistSq + 2.0f*distVec.fX + 1.0f;
276     if (distSq < curr->fDistSq) {
277         distVec.fX += 1.0f;
278         curr->fDistSq = distSq;
279         curr->fDistVector = distVec;
280     }
281 
282     // bottom left
283     check = curr + width-1;
284     distVec = check->fDistVector;
285     distSq = check->fDistSq - 2.0f*(distVec.fX - distVec.fY - 1.0f);
286     if (distSq < curr->fDistSq) {
287         distVec.fX -= 1.0f;
288         distVec.fY += 1.0f;
289         curr->fDistSq = distSq;
290         curr->fDistVector = distVec;
291     }
292 
293     // bottom
294     check = curr + width;
295     distVec = check->fDistVector;
296     distSq = check->fDistSq + 2.0f*distVec.fY + 1.0f;
297     if (distSq < curr->fDistSq) {
298         distVec.fY += 1.0f;
299         curr->fDistSq = distSq;
300         curr->fDistVector = distVec;
301     }
302 
303     // bottom right
304     check = curr + width+1;
305     distVec = check->fDistVector;
306     distSq = check->fDistSq + 2.0f*(distVec.fX + distVec.fY + 1.0f);
307     if (distSq < curr->fDistSq) {
308         distVec.fX += 1.0f;
309         distVec.fY += 1.0f;
310         curr->fDistSq = distSq;
311         curr->fDistVector = distVec;
312     }
313 }
314 
315 // enable this to output edge data rather than the distance field
316 #define DUMP_EDGE 0
317 
318 #if !DUMP_EDGE
319 template <int distanceMagnitude>
pack_distance_field_val(float dist)320 static unsigned char pack_distance_field_val(float dist) {
321     // The distance field is constructed as unsigned char values, so that the zero value is at 128,
322     // Beside 128, we have 128 values in range [0, 128), but only 127 values in range (128, 255].
323     // So we multiply distanceMagnitude by 127/128 at the latter range to avoid overflow.
324     dist = SkScalarPin(-dist, -distanceMagnitude, distanceMagnitude * 127.0f / 128.0f);
325 
326     // Scale into the positive range for unsigned distance.
327     dist += distanceMagnitude;
328 
329     // Scale into unsigned char range.
330     // Round to place negative and positive values as equally as possible around 128
331     // (which represents zero).
332     return (unsigned char)SkScalarRoundToInt(dist / (2 * distanceMagnitude) * 256.0f);
333 }
334 #endif
335 
336 // assumes a padded 8-bit image and distance field
337 // width and height are the original width and height of the image
generate_distance_field_from_image(unsigned char * distanceField,const unsigned char * copyPtr,int width,int height)338 static bool generate_distance_field_from_image(unsigned char* distanceField,
339                                                const unsigned char* copyPtr,
340                                                int width, int height) {
341     SkASSERT(distanceField);
342     SkASSERT(copyPtr);
343 
344     // we expand our temp data by one more on each side to simplify
345     // the scanning code -- will always be treated as infinitely far away
346     int pad = SK_DistanceFieldPad + 1;
347 
348     // set params for distance field data
349     int dataWidth = width + 2*pad;
350     int dataHeight = height + 2*pad;
351 
352     // create zeroed temp DFData+edge storage
353     SkAutoFree storage(sk_calloc_throw(dataWidth*dataHeight*(sizeof(DFData) + 1)));
354     DFData*        dataPtr = (DFData*)storage.get();
355     unsigned char* edgePtr = (unsigned char*)storage.get() + dataWidth*dataHeight*sizeof(DFData);
356 
357     // copy glyph into distance field storage
358     init_glyph_data(dataPtr, edgePtr, copyPtr,
359                     dataWidth, dataHeight,
360                     width+2, height+2, SK_DistanceFieldPad);
361 
362     // create initial distance data, particularly at edges
363     init_distances(dataPtr, edgePtr, dataWidth, dataHeight);
364 
365     // now perform Euclidean distance transform to propagate distances
366 
367     // forwards in y
368     DFData* currData = dataPtr+dataWidth+1; // skip outer buffer
369     unsigned char* currEdge = edgePtr+dataWidth+1;
370     for (int j = 1; j < dataHeight-1; ++j) {
371         // forwards in x
372         for (int i = 1; i < dataWidth-1; ++i) {
373             // don't need to calculate distance for edge pixels
374             if (!*currEdge) {
375                 F1(currData, dataWidth);
376             }
377             ++currData;
378             ++currEdge;
379         }
380 
381         // backwards in x
382         --currData; // reset to end
383         --currEdge;
384         for (int i = 1; i < dataWidth-1; ++i) {
385             // don't need to calculate distance for edge pixels
386             if (!*currEdge) {
387                 F2(currData, dataWidth);
388             }
389             --currData;
390             --currEdge;
391         }
392 
393         currData += dataWidth+1;
394         currEdge += dataWidth+1;
395     }
396 
397     // backwards in y
398     currData = dataPtr+dataWidth*(dataHeight-2) - 1; // skip outer buffer
399     currEdge = edgePtr+dataWidth*(dataHeight-2) - 1;
400     for (int j = 1; j < dataHeight-1; ++j) {
401         // forwards in x
402         for (int i = 1; i < dataWidth-1; ++i) {
403             // don't need to calculate distance for edge pixels
404             if (!*currEdge) {
405                 B1(currData, dataWidth);
406             }
407             ++currData;
408             ++currEdge;
409         }
410 
411         // backwards in x
412         --currData; // reset to end
413         --currEdge;
414         for (int i = 1; i < dataWidth-1; ++i) {
415             // don't need to calculate distance for edge pixels
416             if (!*currEdge) {
417                 B2(currData, dataWidth);
418             }
419             --currData;
420             --currEdge;
421         }
422 
423         currData -= dataWidth-1;
424         currEdge -= dataWidth-1;
425     }
426 
427     // copy results to final distance field data
428     currData = dataPtr + dataWidth+1;
429     currEdge = edgePtr + dataWidth+1;
430     unsigned char *dfPtr = distanceField;
431     for (int j = 1; j < dataHeight-1; ++j) {
432         for (int i = 1; i < dataWidth-1; ++i) {
433 #if DUMP_EDGE
434             float alpha = currData->fAlpha;
435             float edge = 0.0f;
436             if (*currEdge) {
437                 edge = 0.25f;
438             }
439             // blend with original image
440             float result = alpha + (1.0f-alpha)*edge;
441             unsigned char val = sk_float_round2int(255*result);
442             *dfPtr++ = val;
443 #else
444             float dist;
445             if (currData->fAlpha > 0.5f) {
446                 dist = -SkScalarSqrt(currData->fDistSq);
447             } else {
448                 dist = SkScalarSqrt(currData->fDistSq);
449             }
450             *dfPtr++ = pack_distance_field_val<SK_DistanceFieldMagnitude>(dist);
451 #endif
452             ++currData;
453             ++currEdge;
454         }
455         currData += 2;
456         currEdge += 2;
457     }
458 
459     return true;
460 }
461 
462 // assumes an 8-bit image and distance field
SkGenerateDistanceFieldFromA8Image(unsigned char * distanceField,const unsigned char * image,int width,int height,size_t rowBytes)463 bool SkGenerateDistanceFieldFromA8Image(unsigned char* distanceField,
464                                         const unsigned char* image,
465                                         int width, int height, size_t rowBytes) {
466     SkASSERT(distanceField);
467     SkASSERT(image);
468 
469     // create temp data
470     SkAutoSMalloc<1024> copyStorage((width+2)*(height+2)*sizeof(char));
471     unsigned char* copyPtr = (unsigned char*) copyStorage.get();
472 
473     // we copy our source image into a padded copy to ensure we catch edge transitions
474     // around the outside
475     const unsigned char* currSrcScanLine = image;
476     sk_bzero(copyPtr, (width+2)*sizeof(char));
477     unsigned char* currDestPtr = copyPtr + width + 2;
478     for (int i = 0; i < height; ++i) {
479         *currDestPtr++ = 0;
480         memcpy(currDestPtr, currSrcScanLine, rowBytes);
481         currSrcScanLine += rowBytes;
482         currDestPtr += width;
483         *currDestPtr++ = 0;
484     }
485     sk_bzero(currDestPtr, (width+2)*sizeof(char));
486 
487     return generate_distance_field_from_image(distanceField, copyPtr, width, height);
488 }
489 
490 // assumes a 1-bit image and 8-bit distance field
SkGenerateDistanceFieldFromBWImage(unsigned char * distanceField,const unsigned char * image,int width,int height,size_t rowBytes)491 bool SkGenerateDistanceFieldFromBWImage(unsigned char* distanceField,
492                                         const unsigned char* image,
493                                         int width, int height, size_t rowBytes) {
494     SkASSERT(distanceField);
495     SkASSERT(image);
496 
497     // create temp data
498     SkAutoSMalloc<1024> copyStorage((width+2)*(height+2)*sizeof(char));
499     unsigned char* copyPtr = (unsigned char*) copyStorage.get();
500 
501     // we copy our source image into a padded copy to ensure we catch edge transitions
502     // around the outside
503     const unsigned char* currSrcScanLine = image;
504     sk_bzero(copyPtr, (width+2)*sizeof(char));
505     unsigned char* currDestPtr = copyPtr + width + 2;
506     for (int i = 0; i < height; ++i) {
507         *currDestPtr++ = 0;
508         int rowWritesLeft = width;
509         const unsigned char *maskPtr = currSrcScanLine;
510         while (rowWritesLeft > 0) {
511             unsigned mask = *maskPtr++;
512             for (int i = 7; i >= 0 && rowWritesLeft; --i, --rowWritesLeft) {
513                 *currDestPtr++ = (mask & (1 << i)) ? 0xff : 0;
514             }
515         }
516         currSrcScanLine += rowBytes;
517         *currDestPtr++ = 0;
518     }
519     sk_bzero(currDestPtr, (width+2)*sizeof(char));
520 
521     return generate_distance_field_from_image(distanceField, copyPtr, width, height);
522 }
523