static void SaveImgBufferToFile(BitmapAtlasItemSource glyphImg, string filename) { using (PixelFarm.CpuBlit.MemBitmap memBmp = PixelFarm.CpuBlit.MemBitmap.CreateFromCopy( glyphImg.Width, glyphImg.Height, glyphImg.GetImageBuffer(), false)) { PixelFarm.CpuBlit.MemBitmapExtensions.SaveImage(memBmp, filename, PixelFarm.CpuBlit.MemBitmapIO.OutputImageFormat.Png); } }
public void HandleNotFoundGlyph(ushort glyphIndex) { #if DEBUG System.Diagnostics.Debug.WriteLine("bitmap texture,create fallback glyph:" + glyphIndex); #endif //NESTED //draw a glyph into tmpMemBmp and then copy to a GlyphImage //build glyph _outlineBuilder.BuildFromGlyphIndex(glyphIndex, _sizeInPoints); BitmapAtlasItemSource glyphImg = _aggTextureGen.CreateAtlasItem(_outlineBuilder, 1); glyphImg.UniqueInt16Name = glyphIndex; _onEachGlyphDel?.Invoke(glyphImg); _atlasBuilder.AddItemSource(glyphImg); }
public MemBitmap BuildSingleImage(bool flipY) { //1. add to list var itemList = new List <BitmapAtlasItemSource>(_items.Values); int totalMaxLim = MaxAtlasWidth; int maxRowHeight = 0; int currentY = 0; int currentX = 0; switch (this.SpaceCompactOption) { default: throw new System.NotSupportedException(); case CompactOption.BinPack: { //2. sort by glyph width itemList.Sort((a, b) => a.Width.CompareTo(b.Width)); //3. layout for (int i = itemList.Count - 1; i >= 0; --i) { BitmapAtlasItemSource g = itemList[i]; if (g.Height > maxRowHeight) { maxRowHeight = g.Height; } if (currentX + g.Width > totalMaxLim) { //start new row currentY += maxRowHeight; currentX = 0; } //------------------- g.Area = new Rectangle(currentX, currentY, g.Width, g.Height); currentX += g.Width; } } break; case CompactOption.ArrangeByHeight: { //2. sort by height itemList.Sort((a, b) => a.Height.CompareTo(b.Height)); //3. layout int glyphCount = itemList.Count; for (int i = 0; i < glyphCount; ++i) { BitmapAtlasItemSource g = itemList[i]; if (g.Height > maxRowHeight) { maxRowHeight = g.Height; } if (currentX + g.Width > totalMaxLim) { //start new row currentY += maxRowHeight; currentX = 0; maxRowHeight = g.Height; //reset, after start new row } //------------------- g.Area = new Rectangle(currentX, currentY, g.Width, g.Height); currentX += g.Width; } } break; case CompactOption.None: { //3. layout int glyphCount = itemList.Count; for (int i = 0; i < glyphCount; ++i) { BitmapAtlasItemSource g = itemList[i]; if (g.Height > maxRowHeight) { maxRowHeight = g.Height; } if (currentX + g.Width > totalMaxLim) { //start new row currentY += maxRowHeight; currentX = 0; maxRowHeight = g.Height; //reset, after start new row } //------------------- g.Area = new Rectangle(currentX, currentY, g.Width, g.Height); currentX += g.Width; } } break; } currentY += maxRowHeight; int imgH = currentY; // ------------------------------- //compact image location // TODO: review performance here again*** int totalImgWidth = totalMaxLim; if (SpaceCompactOption == CompactOption.BinPack) //again here? { totalImgWidth = 0; //reset //use bin packer BinPacker binPacker = new BinPacker(totalMaxLim, currentY); for (int i = itemList.Count - 1; i >= 0; --i) { BitmapAtlasItemSource g = itemList[i]; BinPackRect newRect = binPacker.Insert(g.Width, g.Height); g.Area = new Rectangle(newRect.X, newRect.Y, g.Width, g.Height); //recalculate proper max midth again, after arrange and compact space if (newRect.Right > totalImgWidth) { totalImgWidth = newRect.Right; } } } // ------------------------------- //4. create a mergeBmpBuffer //please note that original glyph image is head-down (Y axis) //so we will flip-Y axis again in step 5. int[] mergeBmpBuffer = new int[totalImgWidth * imgH]; if (SpaceCompactOption == CompactOption.BinPack) //again here? { for (int i = itemList.Count - 1; i >= 0; --i) { BitmapAtlasItemSource g = itemList[i]; //copy glyph image buffer to specific area of final result buffer CopyToDest(g.GetImageBuffer(), g.Width, g.Height, mergeBmpBuffer, g.Area.Left, g.Area.Top, totalImgWidth); } } else { int itemCount = itemList.Count; for (int i = 0; i < itemCount; ++i) { BitmapAtlasItemSource g = itemList[i]; //copy glyph image buffer to specific area of final result buffer CopyToDest(g.GetImageBuffer(), g.Width, g.Height, mergeBmpBuffer, g.Area.Left, g.Area.Top, totalImgWidth); } } //5. since the mergeBmpBuffer is head-down //we will flipY axis again to head-up, the head-up img is easy to read and debug if (flipY) { int[] totalBufferFlipY = new int[mergeBmpBuffer.Length]; int srcRowIndex = imgH - 1; int strideInBytes = totalImgWidth * 4;//32 argb for (int i = 0; i < imgH; ++i) { //copy each row from src to dst System.Buffer.BlockCopy(mergeBmpBuffer, strideInBytes * srcRowIndex, totalBufferFlipY, strideInBytes * i, strideInBytes); srcRowIndex--; } //flipY on atlas info too for (int i = 0; i < itemList.Count; ++i) { BitmapAtlasItemSource g = itemList[i]; Rectangle rect = g.Area; g.Area = new Rectangle(rect.X, imgH - (rect.Y + rect.Height), rect.Width, rect.Height); } //*** //6. generate final output //TODO: rename GlyphImage to another name to distinquist //between small glyph and a large one return(_latestResultBmp = PixelFarm.CpuBlit.MemBitmap.CreateFromCopy(totalImgWidth, imgH, totalBufferFlipY)); } else { return(_latestResultBmp = PixelFarm.CpuBlit.MemBitmap.CreateFromCopy(totalImgWidth, imgH, mergeBmpBuffer)); } }
/// <summary> /// add or replace /// </summary> /// <param name="itemIndex"></param> /// <param name="img"></param> public void AddItemSource(BitmapAtlasItemSource img) { _items[img.UniqueInt16Name] = img; }
void CreateTextureFontFromGlyphIndices( Typeface typeface, float sizeInPoint, HintTechnique hintTechnique, SimpleBitmapAtlasBuilder atlasBuilder, ushort[] glyphIndices) { //sample: create sample msdf texture //------------------------------------------------------------- var outlineBuilder = new GlyphOutlineBuilder(typeface); outlineBuilder.SetHintTechnique(hintTechnique); // if (atlasBuilder.TextureKind == TextureKind.Msdf) { float pxscale = typeface.CalculateScaleToPixelFromPointSize(sizeInPoint); var msdfGenParams = new Msdfgen.MsdfGenParams(); int j = glyphIndices.Length; if (MsdfGenVersion == 3) { Msdfgen.MsdfGen3 gen3 = new Msdfgen.MsdfGen3(); for (int i = 0; i < j; ++i) { ushort gindex = glyphIndices[i]; //create picture with unscaled version set scale=-1 //(we will create glyph contours and analyze them) outlineBuilder.BuildFromGlyphIndex(gindex, -1); var glyphToVxs = new GlyphTranslatorToVxs(); outlineBuilder.ReadShapes(glyphToVxs); using (Tools.BorrowVxs(out var vxs)) { glyphToVxs.WriteUnFlattenOutput(vxs, pxscale); BitmapAtlasItemSource glyphImg = gen3.GenerateMsdfTexture(vxs); glyphImg.UniqueInt16Name = gindex; _onEachGlyphDel?.Invoke(glyphImg); // atlasBuilder.AddItemSource(glyphImg); } } } else { Msdfgen.MsdfGen3 gen3 = new Msdfgen.MsdfGen3(); for (int i = 0; i < j; ++i) { ushort gindex = glyphIndices[i]; //create picture with unscaled version set scale=-1 //(we will create glyph contours and analyze them) outlineBuilder.BuildFromGlyphIndex(gindex, -1); var glyphToVxs = new GlyphTranslatorToVxs(); outlineBuilder.ReadShapes(glyphToVxs); using (Tools.BorrowVxs(out var vxs)) { glyphToVxs.WriteUnFlattenOutput(vxs, pxscale); BitmapAtlasItemSource glyphImg = gen3.GenerateMsdfTexture(vxs); glyphImg.UniqueInt16Name = gindex; _onEachGlyphDel?.Invoke(glyphImg); atlasBuilder.AddItemSource(glyphImg); } } } } else { AggGlyphTextureGen aggTextureGen = new AggGlyphTextureGen(); aggTextureGen.TextureKind = atlasBuilder.TextureKind; //create reusable agg painter*** //assume each glyph size= 2 * line height //TODO: review here again... int tmpMemBmpHeight = (int)(2 * typeface.CalculateRecommendLineSpacing() * typeface.CalculateScaleToPixelFromPointSize(sizeInPoint)); //create glyph img using (PixelFarm.CpuBlit.MemBitmap tmpMemBmp = new PixelFarm.CpuBlit.MemBitmap(tmpMemBmpHeight, tmpMemBmpHeight)) //square { //draw a glyph into tmpMemBmp and then copy to a GlyphImage aggTextureGen.Painter = PixelFarm.CpuBlit.AggPainter.Create(tmpMemBmp); #if DEBUG tmpMemBmp._dbugNote = "CreateGlyphImage()"; #endif int j = glyphIndices.Length; for (int i = 0; i < j; ++i) { //build glyph ushort gindex = glyphIndices[i]; outlineBuilder.BuildFromGlyphIndex(gindex, sizeInPoint); BitmapAtlasItemSource glyphImg = aggTextureGen.CreateAtlasItem(outlineBuilder, 1); glyphImg.UniqueInt16Name = gindex; _onEachGlyphDel?.Invoke(glyphImg); atlasBuilder.AddItemSource(glyphImg); } } } }
public BitmapAtlasItemSource CreateAtlasItem(GlyphOutlineBuilder builder, float pxscale) { _txToVxs.Reset(); //1. builder read shape and translate it with _txToVxs builder.ReadShapes(_txToVxs); using (Tools.BorrowVxs(out var glyphVxs, out var vxs2)) { //2. write translated data (in the _txToVxs) to glyphVxs _txToVxs.WriteOutput(glyphVxs, pxscale); Q1RectD bounds = glyphVxs.GetBoundingRect(); //-------------------------------------------- int w = (int)System.Math.Ceiling(bounds.Width); int h = (int)System.Math.Ceiling(bounds.Height); if (w < 5) { w = 5; } if (h < 5) { h = 5; } //we need some margin int horizontal_margin = 1; int vertical_margin = 1; //translate to positive quadrant and use minimum space int dx = (int)Math.Ceiling((bounds.Left < 0) ? -bounds.Left : 0); int dy = 0; //vertical adjust =>since we need to move it, then move it with integer value if (bounds.Bottom < 0) { dy = (int)Math.Ceiling(-bounds.Bottom); } else if (bounds.Bottom > 0) { dy = (int)Math.Floor(-bounds.Bottom); } dx += horizontal_margin; //margin left dy += vertical_margin; //-------------------------------------------- w = dx + w + horizontal_margin; //+right margin h = vertical_margin + h + vertical_margin; //+bottom margin AggPainter painter = Painter; if (TextureKind == TextureKind.StencilLcdEffect) { glyphVxs.TranslateToNewVxs(dx + 0.33f, dy, vxs2); //offset to proper x of subpixel rendering *** glyphVxs = vxs2; Q1RectD bounds2 = vxs2.GetBoundingRect(); if (w < bounds2.Right) { w = (int)Math.Ceiling(bounds2.Right); } // painter.UseLcdEffectSubPixelRendering = true; //we use white glyph on black bg for this texture painter.Clear(Color.Black); painter.FillColor = Color.White; //painter.Clear(Color.FromArgb(0, 255, 255, 255)); //white -transparent //painter.FillColor = Color.Black; //painter.Clear(Color.White); //painter.FillColor = Color.Black; //painter.Clear(Color.Empty); //painter.FillColor = Color.Black; painter.Fill(glyphVxs); //apply sharpen filter //painter.DoFilter(new RectInt(0, h, w, 0), 2); //painter.DoFilter(new RectInt(0, h, w, 0), 2); //? } else { glyphVxs.TranslateToNewVxs(dx, dy, vxs2); glyphVxs = vxs2; painter.UseLcdEffectSubPixelRendering = false; if (TextureKind == TextureKind.StencilGreyScale) { painter.Clear(Color.Empty); painter.FillColor = Color.Black; } else { painter.Clear(BackGroundColor); painter.FillColor = this.GlyphColor; } painter.Fill(glyphVxs); } // if (w > painter.RenderSurface.DestBitmap.Width) { w = painter.RenderSurface.DestBitmap.Width; } if (h > painter.RenderSurface.DestBitmap.Height) { h = painter.RenderSurface.DestBitmap.Height; } var glyphImage = new BitmapAtlasItemSource(w, h); #if DEBUG if (dx < short.MinValue || dx > short.MaxValue) { throw new NotSupportedException(); } if (dy < short.MinValue || dy > short.MaxValue) { throw new NotSupportedException(); } #endif glyphImage.TextureXOffset = (short)dx; glyphImage.TextureYOffset = (short)dy; glyphImage.SetImageBuffer(MemBitmapExt.CopyImgBuffer(painter.RenderSurface.DestBitmap, w, h), false); //copy data from agg canvas to glyph image return(glyphImage); } }
void CreateTextureFontFromGlyphIndices( HintTechnique hintTechnique, SimpleBitmapAtlasBuilder atlasBuilder, ushort[] glyphIndices) { //sample: create sample msdf texture //------------------------------------------------------------- var outlineBuilder = new GlyphOutlineBuilder(_typeface); outlineBuilder.SetHintTechnique(hintTechnique); // AggGlyphTextureGen aggTextureGen = new AggGlyphTextureGen(); GlyphNotFoundHelper glyphNotFoundHelper = new GlyphNotFoundHelper(atlasBuilder, outlineBuilder, _onEachGlyphDel, aggTextureGen, _sizeInPoints); //create reusable agg painter*** //assume each glyph size= 2 * line height //TODO: review here again... //please note that DPI effect glyph size //*** int tmpMemBmpHeight = (int)(2 * _typeface.CalculateRecommendLineSpacing() * _px_scale); // if (atlasBuilder.TextureKind == TextureKind.Msdf) { var msdfGenParams = new Msdfgen.MsdfGenParams(); int j = glyphIndices.Length; if (MsdfGenVersion == 3) { Msdfgen.MsdfGen3 gen3 = new Msdfgen.MsdfGen3(); for (int i = 0; i < j; ++i) { ushort gindex = glyphIndices[i]; //create picture with unscaled version set scale=-1 //(we will create glyph contours and analyze them) var glyphToVxs = new GlyphTranslatorToVxs(); outlineBuilder.BuildFromGlyphIndex(gindex, -1, glyphToVxs); using (Tools.BorrowVxs(out var vxs)) { glyphToVxs.WriteUnFlattenOutput(vxs, _px_scale); BitmapAtlasItemSource glyphImg = gen3.GenerateMsdfTexture(vxs); glyphImg.UniqueInt16Name = gindex; _onEachGlyphDel?.Invoke(glyphImg); // atlasBuilder.AddItemSource(glyphImg); } } } else { //use gen3 Msdfgen.MsdfGen3 gen3 = new Msdfgen.MsdfGen3(); for (int i = 0; i < j; ++i) { ushort gindex = glyphIndices[i]; //create picture with unscaled version set scale=-1 //(we will create glyph contours and analyze them) var glyphToVxs = new GlyphTranslatorToVxs(); outlineBuilder.BuildFromGlyphIndex(gindex, -1, glyphToVxs); using (Tools.BorrowVxs(out var vxs)) { glyphToVxs.WriteUnFlattenOutput(vxs, _px_scale); BitmapAtlasItemSource glyphImg = gen3.GenerateMsdfTexture(vxs); glyphImg.UniqueInt16Name = gindex; _onEachGlyphDel?.Invoke(glyphImg); atlasBuilder.AddItemSource(glyphImg); } } } return; } else if (atlasBuilder.TextureKind == TextureKind.Bitmap) { //generate color bitmap atlas int j = glyphIndices.Length; GlyphMeshStore glyphMeshStore = new GlyphMeshStore(); glyphMeshStore.SetFont(_typeface, _sizeInPoints); aggTextureGen.TextureKind = TextureKind.Bitmap; using (PixelFarm.CpuBlit.MemBitmap tmpMemBmp = new PixelFarm.CpuBlit.MemBitmap(tmpMemBmpHeight, tmpMemBmpHeight)) //square { aggTextureGen.Painter = PixelFarm.CpuBlit.AggPainter.Create(tmpMemBmp); #if DEBUG tmpMemBmp._dbugNote = "CreateGlyphImage()"; #endif if (_typeface.HasColorTable()) { //outline glyph for (int i = 0; i < j; ++i) { ushort gindex = glyphIndices[i]; if (!_typeface.COLRTable.LayerIndices.TryGetValue(gindex, out ushort colorLayerStart)) { //not found, then render as normal //TODO: impl //create glyph img glyphNotFoundHelper.HandleNotFoundGlyph(gindex); } else { //TODO: review this again GlyphBitmap glyphBmp = GetGlyphBitmapFromColorOutlineGlyph(gindex, glyphMeshStore, colorLayerStart); if (glyphBmp == null) { glyphNotFoundHelper.HandleNotFoundGlyph(gindex); } else { int w = glyphBmp.Width; int h = glyphBmp.Height; BitmapAtlasItemSource glyphImage = new BitmapAtlasItemSource(glyphBmp.Width, glyphBmp.Height); glyphImage.TextureXOffset = (short)glyphBmp.ImageStartX; glyphImage.TextureYOffset = (short)glyphBmp.ImageStartY; // glyphImage.SetImageBuffer(MemBitmapExt.CopyImgBuffer(glyphBmp.Bitmap, w, h, true), false); glyphImage.UniqueInt16Name = gindex; _onEachGlyphDel?.Invoke(glyphImage); atlasBuilder.AddItemSource(glyphImage); //clear glyphBmp.Bitmap.Dispose(); glyphBmp.Bitmap = null; } } } } else if (_typeface.IsBitmapFont) { aggTextureGen.TextureKind = TextureKind.Bitmap; //test this with Noto Color Emoji for (int i = 0; i < j; ++i) { ushort gindex = glyphIndices[i]; GlyphBitmap glyphBmp = GetGlyphBitmapFromBitmapFont(gindex); if (glyphBmp == null) { glyphNotFoundHelper.HandleNotFoundGlyph(gindex); } else { int w = glyphBmp.Width; int h = glyphBmp.Height; BitmapAtlasItemSource glyphImage = new BitmapAtlasItemSource(glyphBmp.Width, glyphBmp.Height); glyphImage.TextureXOffset = (short)glyphBmp.ImageStartX; glyphImage.TextureYOffset = (short)glyphBmp.ImageStartY; // glyphImage.SetImageBuffer(MemBitmapExt.CopyImgBuffer(glyphBmp.Bitmap, w, h, true), false); glyphImage.UniqueInt16Name = gindex; _onEachGlyphDel?.Invoke(glyphImage); atlasBuilder.AddItemSource(glyphImage); //clear glyphBmp.Bitmap.Dispose(); glyphBmp.Bitmap = null; } } } else if (_typeface.HasSvgTable()) { aggTextureGen.TextureKind = TextureKind.Bitmap; //test this with TwitterEmoji //generate membitmap from svg #if DEBUG System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch(); sw1.Start(); #endif for (int i = 0; i < j; ++i) { //TODO: add mutli-threads / async version ushort gindex = glyphIndices[i]; GlyphBitmap glyphBmp = GetGlyphBitmapFromSvg(gindex); if (glyphBmp == null) { glyphNotFoundHelper.HandleNotFoundGlyph(gindex); } else { int w = glyphBmp.Width; int h = glyphBmp.Height; BitmapAtlasItemSource glyphImage = new BitmapAtlasItemSource(glyphBmp.Width, glyphBmp.Height); glyphImage.TextureXOffset = (short)glyphBmp.ImageStartX; glyphImage.TextureYOffset = (short)glyphBmp.ImageStartY; // glyphImage.SetImageBuffer(MemBitmapExt.CopyImgBuffer(glyphBmp.Bitmap, w, h, true), false); glyphImage.UniqueInt16Name = gindex; _onEachGlyphDel?.Invoke(glyphImage); atlasBuilder.AddItemSource(glyphImage); //clear glyphBmp.Bitmap.Dispose(); glyphBmp.Bitmap = null; } } #if DEBUG sw1.Stop(); long ms = sw1.ElapsedMilliseconds; #endif } return; //NO go below //*** } //END using } //--------------------------- //OTHERS.... { aggTextureGen.TextureKind = atlasBuilder.TextureKind; //create glyph img using (PixelFarm.CpuBlit.MemBitmap tmpMemBmp = new PixelFarm.CpuBlit.MemBitmap(tmpMemBmpHeight, tmpMemBmpHeight)) //square { //draw a glyph into tmpMemBmp and then copy to a GlyphImage aggTextureGen.Painter = PixelFarm.CpuBlit.AggPainter.Create(tmpMemBmp); #if DEBUG tmpMemBmp._dbugNote = "CreateGlyphImage()"; #endif int j = glyphIndices.Length; for (int i = 0; i < j; ++i) { //build glyph ushort gindex = glyphIndices[i]; outlineBuilder.BuildFromGlyphIndex(gindex, _sizeInPoints); BitmapAtlasItemSource glyphImg = aggTextureGen.CreateAtlasItem(outlineBuilder, 1); glyphImg.UniqueInt16Name = gindex; _onEachGlyphDel?.Invoke(glyphImg); atlasBuilder.AddItemSource(glyphImg); } } } }