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
pack_distance_field_val(float dist,float distanceMagnitude)319 static unsigned char pack_distance_field_val(float dist, float distanceMagnitude) {
320     if (dist <= -distanceMagnitude) {
321         return 255;
322     } else if (dist > distanceMagnitude) {
323         return 0;
324     } else {
325         return (unsigned char)((distanceMagnitude-dist)*128.0f/distanceMagnitude);
326     }
327 }
328 #endif
329 
330 // assumes a padded 8-bit image and distance field
331 // 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)332 static bool generate_distance_field_from_image(unsigned char* distanceField,
333                                                const unsigned char* copyPtr,
334                                                int width, int height) {
335     SkASSERT(distanceField);
336     SkASSERT(copyPtr);
337 
338     // we expand our temp data by one more on each side to simplify
339     // the scanning code -- will always be treated as infinitely far away
340     int pad = SK_DistanceFieldPad + 1;
341 
342     // set params for distance field data
343     int dataWidth = width + 2*pad;
344     int dataHeight = height + 2*pad;
345 
346     // create temp data
347     size_t dataSize = dataWidth*dataHeight*sizeof(DFData);
348     SkAutoSMalloc<1024> dfStorage(dataSize);
349     DFData* dataPtr = (DFData*) dfStorage.get();
350     sk_bzero(dataPtr, dataSize);
351 
352     SkAutoSMalloc<1024> edgeStorage(dataWidth*dataHeight*sizeof(char));
353     unsigned char* edgePtr = (unsigned char*) edgeStorage.get();
354     sk_bzero(edgePtr, dataWidth*dataHeight*sizeof(char));
355 
356     // copy glyph into distance field storage
357     init_glyph_data(dataPtr, edgePtr, copyPtr,
358                     dataWidth, dataHeight,
359                     width+2, height+2, SK_DistanceFieldPad);
360 
361     // create initial distance data, particularly at edges
362     init_distances(dataPtr, edgePtr, dataWidth, dataHeight);
363 
364     // now perform Euclidean distance transform to propagate distances
365 
366     // forwards in y
367     DFData* currData = dataPtr+dataWidth+1; // skip outer buffer
368     unsigned char* currEdge = edgePtr+dataWidth+1;
369     for (int j = 1; j < dataHeight-1; ++j) {
370         // forwards in x
371         for (int i = 1; i < dataWidth-1; ++i) {
372             // don't need to calculate distance for edge pixels
373             if (!*currEdge) {
374                 F1(currData, dataWidth);
375             }
376             ++currData;
377             ++currEdge;
378         }
379 
380         // backwards in x
381         --currData; // reset to end
382         --currEdge;
383         for (int i = 1; i < dataWidth-1; ++i) {
384             // don't need to calculate distance for edge pixels
385             if (!*currEdge) {
386                 F2(currData, dataWidth);
387             }
388             --currData;
389             --currEdge;
390         }
391 
392         currData += dataWidth+1;
393         currEdge += dataWidth+1;
394     }
395 
396     // backwards in y
397     currData = dataPtr+dataWidth*(dataHeight-2) - 1; // skip outer buffer
398     currEdge = edgePtr+dataWidth*(dataHeight-2) - 1;
399     for (int j = 1; j < dataHeight-1; ++j) {
400         // forwards in x
401         for (int i = 1; i < dataWidth-1; ++i) {
402             // don't need to calculate distance for edge pixels
403             if (!*currEdge) {
404                 B1(currData, dataWidth);
405             }
406             ++currData;
407             ++currEdge;
408         }
409 
410         // backwards in x
411         --currData; // reset to end
412         --currEdge;
413         for (int i = 1; i < dataWidth-1; ++i) {
414             // don't need to calculate distance for edge pixels
415             if (!*currEdge) {
416                 B2(currData, dataWidth);
417             }
418             --currData;
419             --currEdge;
420         }
421 
422         currData -= dataWidth-1;
423         currEdge -= dataWidth-1;
424     }
425 
426     // copy results to final distance field data
427     currData = dataPtr + dataWidth+1;
428     currEdge = edgePtr + dataWidth+1;
429     unsigned char *dfPtr = distanceField;
430     for (int j = 1; j < dataHeight-1; ++j) {
431         for (int i = 1; i < dataWidth-1; ++i) {
432 #if DUMP_EDGE
433             float alpha = currData->fAlpha;
434             float edge = 0.0f;
435             if (*currEdge) {
436                 edge = 0.25f;
437             }
438             // blend with original image
439             float result = alpha + (1.0f-alpha)*edge;
440             unsigned char val = sk_float_round2int(255*result);
441             *dfPtr++ = val;
442 #else
443             float dist;
444             if (currData->fAlpha > 0.5f) {
445                 dist = -SkScalarSqrt(currData->fDistSq);
446             } else {
447                 dist = SkScalarSqrt(currData->fDistSq);
448             }
449             *dfPtr++ = pack_distance_field_val(dist, (float)SK_DistanceFieldMagnitude);
450 #endif
451             ++currData;
452             ++currEdge;
453         }
454         currData += 2;
455         currEdge += 2;
456     }
457 
458     return true;
459 }
460 
461 // assumes an 8-bit image and distance field
SkGenerateDistanceFieldFromA8Image(unsigned char * distanceField,const unsigned char * image,int width,int height,size_t rowBytes)462 bool SkGenerateDistanceFieldFromA8Image(unsigned char* distanceField,
463                                         const unsigned char* image,
464                                         int width, int height, size_t rowBytes) {
465     SkASSERT(distanceField);
466     SkASSERT(image);
467 
468     // create temp data
469     SkAutoSMalloc<1024> copyStorage((width+2)*(height+2)*sizeof(char));
470     unsigned char* copyPtr = (unsigned char*) copyStorage.get();
471 
472     // we copy our source image into a padded copy to ensure we catch edge transitions
473     // around the outside
474     const unsigned char* currSrcScanLine = image;
475     sk_bzero(copyPtr, (width+2)*sizeof(char));
476     unsigned char* currDestPtr = copyPtr + width + 2;
477     for (int i = 0; i < height; ++i) {
478         *currDestPtr++ = 0;
479         memcpy(currDestPtr, currSrcScanLine, rowBytes);
480         currSrcScanLine += rowBytes;
481         currDestPtr += width;
482         *currDestPtr++ = 0;
483     }
484     sk_bzero(currDestPtr, (width+2)*sizeof(char));
485 
486     return generate_distance_field_from_image(distanceField, copyPtr, width, height);
487 }
488 
489 // 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)490 bool SkGenerateDistanceFieldFromBWImage(unsigned char* distanceField,
491                                         const unsigned char* image,
492                                         int width, int height, size_t rowBytes) {
493     SkASSERT(distanceField);
494     SkASSERT(image);
495 
496     // create temp data
497     SkAutoSMalloc<1024> copyStorage((width+2)*(height+2)*sizeof(char));
498     unsigned char* copyPtr = (unsigned char*) copyStorage.get();
499 
500     // we copy our source image into a padded copy to ensure we catch edge transitions
501     // around the outside
502     const unsigned char* currSrcScanLine = image;
503     sk_bzero(copyPtr, (width+2)*sizeof(char));
504     unsigned char* currDestPtr = copyPtr + width + 2;
505     for (int i = 0; i < height; ++i) {
506         *currDestPtr++ = 0;
507         int rowWritesLeft = width;
508         const unsigned char *maskPtr = currSrcScanLine;
509         while (rowWritesLeft > 0) {
510             unsigned mask = *maskPtr++;
511             for (int i = 7; i >= 0 && rowWritesLeft; --i, --rowWritesLeft) {
512                 *currDestPtr++ = (mask & (1 << i)) ? 0xff : 0;
513             }
514         }
515         currSrcScanLine += rowBytes;
516         *currDestPtr++ = 0;
517     }
518     sk_bzero(currDestPtr, (width+2)*sizeof(char));
519 
520     return generate_distance_field_from_image(distanceField, copyPtr, width, height);
521 }
522