static void GenerateMSDF3(FloatRGBBmp output, Shape shape, double range, Vector2 scale, Vector2 translate, double edgeThreshold, EdgeBmpLut lut) { //---------------------- //this is our extension, //we use lookup bitmap (lut) to check //what is the nearest contour of a given pixel. //---------------------- int w = output.Width; int h = output.Height; EdgeSegment[] singleSegment = new EdgeSegment[1];//temp array for for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { //PER-PIXEL-OPERATION //check preview pixel int lutPix = lut.GetPixel(x, y); int lutPixR = (lutPix & 0xFF); int lutPixG = (lutPix >> 8) & 0xff; int lutPixB = (lutPix >> 16) & 0xff; if (lutPixG == 0) { continue; //black=> completely outside, skip } if (lutPixG == EdgeBmpLut.AREA_INSIDE_COVERAGE100 || lutPixG == EdgeBmpLut.AREA_INSIDE_COVERAGE50 || lutPixG == EdgeBmpLut.AREA_INSIDE_COVERAGEX) { //inside the contour => fill all with white output.SetPixel(x, y, new FloatRGB(1f, 1f, 1f)); continue; } //reset variables EdgePoint r = new EdgePoint { minDistance = SignedDistance.INFINITE }, g = new EdgePoint { minDistance = SignedDistance.INFINITE }, b = new EdgePoint { minDistance = SignedDistance.INFINITE }; bool useR, useG, useB; useR = useG = useB = true; //------ Vector2 p = (new Vector2(x + .5, y + .5) / scale) - translate; EdgeStructure edgeStructure = lut.GetEdgeStructure(x, y); #if DEBUG if (edgeStructure.IsEmpty) { //should not occurs throw new NotSupportedException(); } #endif EdgeSegment[] edges = null; if (edgeStructure.HasOverlappedSegments) { edges = edgeStructure.Segments; } else { singleSegment[0] = edgeStructure.Segment; edges = singleSegment; } //------------- for (int i = 0; i < edges.Length; ++i) { EdgeSegment edge = edges[i]; SignedDistance distance = edge.signedDistance(p, out double param);//*** if (edge.HasComponent(EdgeColor.RED) && distance < r.minDistance) { r.minDistance = distance; r.nearEdge = edge; r.nearParam = param; useR = false; } if (edge.HasComponent(EdgeColor.GREEN) && distance < g.minDistance) { g.minDistance = distance; g.nearEdge = edge; g.nearParam = param; useG = false; } if (edge.HasComponent(EdgeColor.BLUE) && distance < b.minDistance) { b.minDistance = distance; b.nearEdge = edge; b.nearParam = param; useB = false; } } double contour_r = r.CalculateContourColor(p); double contour_g = g.CalculateContourColor(p); double contour_b = b.CalculateContourColor(p); if (useB && contour_b <= SignedDistance.INFINITE.distance) { contour_b = 1 * range; } if (useG && contour_g <= SignedDistance.INFINITE.distance) { contour_g = 1 * range; } if (useR && contour_r <= SignedDistance.INFINITE.distance) { contour_r = 1 * range; } output.SetPixel(x, y, new FloatRGB( (float)(contour_r / range + .5), (float)(contour_g / range + .5), (float)(contour_b / range + .5) )); } } }
internal static PixelFarm.CpuBlit.BitmapAtlas.BitmapAtlasItemSource CreateMsdfImage(Shape shape, MsdfGenParams genParams, int w, int h, Vector2 translate, EdgeBmpLut lutBuffer = null) { double edgeThreshold = genParams.edgeThreshold; if (edgeThreshold < 0) { edgeThreshold = 1.00000001; //use default if edgeThreshold <0 } var scale = new Vector2(genParams.scaleX, genParams.scaleY); //scale double range = genParams.pxRange / Math.Min(scale.x, scale.y); //--------- FloatRGBBmp frgbBmp = new FloatRGBBmp(w, h); EdgeColoring.edgeColoringSimple(shape, genParams.angleThreshold); bool flipY = false; if (lutBuffer != null) { GenerateMSDF3(frgbBmp, shape, range, scale, translate,//translate to positive quadrant edgeThreshold, lutBuffer); flipY = shape.InverseYAxis; } else { //use original msdf MsdfGenerator.generateMSDF(frgbBmp, shape, range, scale, translate,//translate to positive quadrant edgeThreshold); } return(new PixelFarm.CpuBlit.BitmapAtlas.BitmapAtlasItemSource(w, h) { Source = ConvertToIntBmp(frgbBmp, flipY), TextureXOffset = (float)translate.x, TextureYOffset = (float)translate.y }); }
public PixelFarm.CpuBlit.BitmapAtlas.BitmapAtlasItemSource GenerateMsdfTexture(VertexStore vxs) { Shape shape = CreateShape(vxs, out EdgeBmpLut edgeBmpLut); if (MsdfGenParams == null) { MsdfGenParams = new MsdfGenParams();//use default } //---preview v1 bounds----------- PreviewSizeAndLocation( shape, MsdfGenParams, out int imgW, out int imgH, out Vector2 translateVec); _dx = translateVec.x; _dy = translateVec.y; //------------------------------------ List <ContourCorner> corners = edgeBmpLut.Corners; TranslateCorners(corners, _dx, _dy); //[1] create lookup table (lut) bitmap that contains area/corner/shape information //each pixel inside it contains data that map to area/corner/shape // using (MemBitmap bmpLut = new MemBitmap(imgW, imgH)) using (Tools.BorrowAggPainter(bmpLut, out var painter)) using (Tools.BorrowShapeBuilder(out var sh)) { _msdfEdgePxBlender.ClearOverlapList();//reset painter.RenderSurface.SetCustomPixelBlender(_msdfEdgePxBlender); //1. clear all bg to black painter.Clear(PixelFarm.Drawing.Color.Black); sh.InitVxs(vxs) //... .TranslateToNewVxs(_dx, _dy) .Flatten(); //--------- //2. force fill the shape (this include hole(s) inside shape to) //( we set threshold to 50 and do force fill) painter.RenderSurface.SetGamma(_prebuiltThresholdGamma_50); _msdfEdgePxBlender.FillMode = MsdfEdgePixelBlender.BlenderFillMode.Force; painter.Fill(sh.CurrentSharedVxs, EdgeBmpLut.EncodeToColor(0, AreaKind.AreaInsideCoverage50)); painter.RenderSurface.SetGamma(_prebuiltThresholdGamma_50);//restore #if DEBUG //debug for output //painter.Fill(v7, Color.Red); //bmpLut.SaveImage("dbug_step0.png"); //int curr_step = 1; #endif //--------- int cornerCount = corners.Count; List <int> cornerOfNextContours = edgeBmpLut.CornerOfNextContours; int startAt = 0; int n = 1; int corner_index = 1; for (int cnt_index = 0; cnt_index < cornerOfNextContours.Count; ++cnt_index) { //contour scope int next_corner_startAt = cornerOfNextContours[cnt_index]; //----------- //AA-borders of the contour painter.RenderSurface.SetGamma(_prebuiltThresholdGamma_OverlappedBorder); //this creates overlapped area for (; n < next_corner_startAt; ++n) { //0-> 1 //1->2 ... n FillBorders(painter, corners[n - 1], corners[n]); #if DEBUG //bmpLut.SaveImage("dbug_step" + curr_step + ".png"); //curr_step++; #endif } { //the last one //close contour, n-> 0 FillBorders(painter, corners[next_corner_startAt - 1], corners[startAt]); #if DEBUG //bmpLut.SaveImage("dbug_step" + curr_step + ".png"); //curr_step++; #endif } startAt = next_corner_startAt; n++; corner_index++; } #if DEBUG //bmpLut.SaveImage("dbug_step2.png"); #endif //painter.RenderSurface.SetGamma(_prebuiltThresholdGamma_100); //_msdfEdgePxBlender.FillMode = MsdfEdgePixelBlender.BlenderFillMode.InnerAreaX; //painter.Fill(sh.CurrentSharedVxs, EdgeBmpLut.EncodeToColor(0, AreaKind.AreaInsideCoverage100)); painter.RenderSurface.SetCustomPixelBlender(null); painter.RenderSurface.SetGamma(null); // List <CornerList> overlappedList = MakeUniqueList(_msdfEdgePxBlender._overlapList); edgeBmpLut.SetOverlappedList(overlappedList); #if DEBUG if (dbugWriteMsdfTexture) { //save for debug //we save to msdf_shape_lut2.png //and check it from external program //but we generate msdf bitmap from msdf_shape_lut.png bmpLut.SaveImage(dbug_msdf_shape_lutName); var bmp5 = MemBitmap.LoadBitmap(dbug_msdf_shape_lutName); int[] lutBuffer5 = bmp5.CopyImgBuffer(bmpLut.Width, bmpLut.Height); if (bmpLut.Width == 338 && bmpLut.Height == 477) { dbugBreak = true; } edgeBmpLut.SetBmpBuffer(bmpLut.Width, bmpLut.Height, lutBuffer5); //generate actual sprite PixelFarm.CpuBlit.BitmapAtlas.BitmapAtlasItemSource item = CreateMsdfImage(shape, MsdfGenParams, imgW, imgH, translateVec, edgeBmpLut); //save msdf bitmap to file using (MemBitmap memBmp = MemBitmap.CreateFromCopy(item.Width, item.Height, item.Source)) { memBmp.SaveImage(dbug_msdf_output); } return(item); } #endif //[B] after we have a lookup table int[] lutBuffer = bmpLut.CopyImgBuffer(bmpLut.Width, bmpLut.Height); edgeBmpLut.SetBmpBuffer(bmpLut.Width, bmpLut.Height, lutBuffer); return(CreateMsdfImage(shape, MsdfGenParams, imgW, imgH, translateVec, edgeBmpLut)); } }
static Shape CreateShape(VertexStore vxs, out EdgeBmpLut bmpLut) { List <EdgeSegment> flattenEdges = new List <EdgeSegment>(); Shape shape = new Shape(); //start with blank shape int i = 0; double x, y; VertexCmd cmd; Contour cnt = null; double latestMoveToX = 0; double latestMoveToY = 0; double latestX = 0; double latestY = 0; List <ContourCorner> corners = new List <ContourCorner>(); List <int> edgeOfNextContours = new List <int>(); List <int> cornerOfNextContours = new List <int>(); while ((cmd = vxs.GetVertex(i, out x, out y)) != VertexCmd.NoMore) { switch (cmd) { case VertexCmd.Close: { //close current cnt if ((latestMoveToX != latestX) || (latestMoveToY != latestY)) { //add line to close the shape if (cnt != null) { flattenEdges.Add(cnt.AddLine(latestX, latestY, latestMoveToX, latestMoveToY)); } } if (cnt != null) { //*** CreateCorners(cnt, corners); edgeOfNextContours.Add(flattenEdges.Count); cornerOfNextContours.Add(corners.Count); shape.contours.Add(cnt); //*** cnt = null; } } break; case VertexCmd.C3: { //C3 curve (Quadratic) if (cnt == null) { cnt = new Contour(); } VertexCmd cmd1 = vxs.GetVertex(i + 1, out double x1, out double y1); i++; if (cmd1 != VertexCmd.LineTo) { throw new NotSupportedException(); } //in this version, //we convert Quadratic to Cubic (https://stackoverflow.com/questions/9485788/convert-quadratic-curve-to-cubic-curve) //Control1X = StartX + ((2f/3) * (ControlX - StartX)) //Control2X = EndX + ((2f/3) * (ControlX - EndX)) //flattenEdges.Add(cnt.AddCubicSegment( // latestX, latestY, // ((2f / 3) * (x - latestX)) + latestX, ((2f / 3) * (y - latestY)) + latestY, // ((2f / 3) * (x - x1)) + x1, ((2f / 3) * (y - y1)) + y1, // x1, y1)); flattenEdges.Add(cnt.AddQuadraticSegment(latestX, latestY, x, y, x1, y1)); latestX = x1; latestY = y1; } break; case VertexCmd.C4: { //C4 curve (Cubic) if (cnt == null) { cnt = new Contour(); } VertexCmd cmd1 = vxs.GetVertex(i + 1, out double x2, out double y2); VertexCmd cmd2 = vxs.GetVertex(i + 2, out double x3, out double y3); i += 2; if (cmd1 != VertexCmd.C4 || cmd2 != VertexCmd.LineTo) { throw new NotSupportedException(); } flattenEdges.Add(cnt.AddCubicSegment(latestX, latestY, x, y, x2, y2, x3, y3)); latestX = x3; latestY = y3; } break; case VertexCmd.LineTo: { if (cnt == null) { cnt = new Contour(); } LinearSegment lineseg = cnt.AddLine(latestX, latestY, x, y); flattenEdges.Add(lineseg); latestX = x; latestY = y; } break; case VertexCmd.MoveTo: { latestX = latestMoveToX = x; latestY = latestMoveToY = y; if (cnt != null) { shape.contours.Add(cnt); cnt = null; } } break; } i++; } if (cnt != null) { shape.contours.Add(cnt); CreateCorners(cnt, corners); edgeOfNextContours.Add(flattenEdges.Count); cornerOfNextContours.Add(corners.Count); cnt = null; } GroupingOverlapContours(shape); //from a given shape we create a corner-arm for each corner bmpLut = new EdgeBmpLut(corners, flattenEdges, edgeOfNextContours, cornerOfNextContours); return(shape); }
unsafe void CustomBlendPixel32(int *dstPtr, Color srcColor) { if (FillMode == BlenderFillMode.Force) { *dstPtr = srcColor.ToARGB(); return; } //------------------------------------------------------------- int srcColorABGR = (int)srcColor.ToABGR(); int existingColor = *dstPtr; //int existing_R = (existingColor >> CO.R_SHIFT) & 0xFF; int existing_G = (existingColor >> PixelFarm.Drawing.Internal.CO.G_SHIFT) & 0xFF; //int existing_B = (existingColor >> CO.B_SHIFT) & 0xFF; if (FillMode == BlenderFillMode.InnerAreaX) { if (existing_G == EdgeBmpLut.BORDER_OUTSIDE || existing_G == EdgeBmpLut.BORDER_OVERLAP_OUTSIDE) { *dstPtr = srcColor.ToARGB(); } return; } if (existingColor == BLACK) { *dstPtr = srcColor.ToARGB(); return; } if (existingColor == _areaInside100) { *dstPtr = srcColor.ToARGB(); return; } if (srcColorABGR == existingColor) { return; } if (FillMode == BlenderFillMode.InnerArea50) { *dstPtr = srcColor.ToARGB(); return; } //------------------------------------------------------------- //decode edge information //we use 2 bytes for encode edge number ushort existingEdgeNo = EdgeBmpLut.DecodeEdgeFromColor(existingColor, out AreaKind existingAreaKind); ushort newEdgeNo = EdgeBmpLut.DecodeEdgeFromColor(srcColor, out AreaKind newEdgeAreaKind); if (newEdgeAreaKind == AreaKind.OverlapInside || newEdgeAreaKind == AreaKind.OverlapOutside) { //new color is overlap color if (existingAreaKind == AreaKind.OverlapInside || existingAreaKind == AreaKind.OverlapOutside) { CornerList registerList = _overlapList[newEdgeNo]; _overlapList[existingEdgeNo].Append(registerList); } else { CornerList registerList = _overlapList[newEdgeNo]; registerList.Append(existingEdgeNo); *dstPtr = EdgeBmpLut.EncodeToColor(newEdgeNo, (existing_G == EdgeBmpLut.BORDER_INSIDE) ? AreaKind.OverlapInside : AreaKind.OverlapOutside).ToARGB(); } } else { if (existingAreaKind == AreaKind.OverlapInside || existingAreaKind == AreaKind.OverlapOutside) { _overlapList[existingEdgeNo].Append(newEdgeNo); } else { OverlapPart overlapPart; AreaKind areaKind; if (existingAreaKind == AreaKind.BorderInside || existingAreaKind == AreaKind.AreaInsideCoverage100) { if (newEdgeAreaKind == AreaKind.BorderInside) { areaKind = AreaKind.OverlapInside; overlapPart = new OverlapPart( existingEdgeNo, (existing_G == EdgeBmpLut.BORDER_INSIDE) ? AreaKind.OverlapInside : AreaKind.OverlapOutside, newEdgeNo, (srcColor.G == EdgeBmpLut.BORDER_INSIDE) ? AreaKind.OverlapInside : AreaKind.OverlapOutside); } else { areaKind = AreaKind.OverlapInside; overlapPart = new OverlapPart( existingEdgeNo, (existing_G == EdgeBmpLut.BORDER_INSIDE) ? AreaKind.OverlapInside : AreaKind.OverlapOutside, newEdgeNo, (existing_G == EdgeBmpLut.BORDER_INSIDE) ? AreaKind.OverlapInside : AreaKind.OverlapOutside); } } else { //existing is outside if (newEdgeAreaKind == AreaKind.BorderInside) { areaKind = AreaKind.OverlapInside; overlapPart = new OverlapPart( existingEdgeNo, (existing_G == EdgeBmpLut.BORDER_INSIDE) ? AreaKind.OverlapInside : AreaKind.OverlapOutside, newEdgeNo, (existing_G == EdgeBmpLut.BORDER_INSIDE) ? AreaKind.OverlapInside : AreaKind.OverlapOutside); } else { areaKind = AreaKind.OverlapOutside; overlapPart = new OverlapPart( existingEdgeNo, (existing_G == EdgeBmpLut.BORDER_INSIDE) ? AreaKind.OverlapInside : AreaKind.OverlapOutside, newEdgeNo, (srcColor.G == EdgeBmpLut.BORDER_INSIDE) ? AreaKind.OverlapInside : AreaKind.OverlapOutside); } } if (!_overlapParts.TryGetValue(overlapPart, out ushort found)) { if (_overlapList.Count >= ushort.MaxValue) { throw new NotSupportedException(); } // ushort newPartNo = (ushort)_overlapList.Count; _overlapParts.Add(overlapPart, newPartNo); // CornerList cornerList = new CornerList(); #if DEBUG if (_overlapList.Count >= 388) { } #endif _overlapList.Add(cornerList); cornerList.Append(existingEdgeNo); cornerList.Append(newEdgeNo); //set new color *dstPtr = EdgeBmpLut.EncodeToColor(newPartNo, areaKind).ToARGB(); } else { //set new color *dstPtr = EdgeBmpLut.EncodeToColor(found, areaKind).ToARGB(); } } } }