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