public TexturePage(string filePath) { var bitmap = new QBitmap(filePath); CreateTexture(bitmap.BitmapData); bitmap.Free(); }
private static void RetargetGlyphRectangleInwards(BitmapData bitmapData, QFontGlyph glyph, bool setYOffset, byte alphaTolerance) { int startX, endX; int startY, endY; var rect = glyph.rect; EmptyDel emptyPix; if (bitmapData.PixelFormat == PixelFormat.Format32bppArgb) emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance); }; else emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyPixel(data, x, y); }; unsafe { for (startX = rect.X; startX < bitmapData.Width; startX++) for (int j = rect.Y; j < rect.Y + rect.Height; j++) if (!emptyPix(bitmapData, startX, j)) goto Done1; Done1: for (endX = rect.X + rect.Width; endX >= 0; endX--) for (int j = rect.Y; j < rect.Y + rect.Height; j++) if (!emptyPix(bitmapData, endX, j)) goto Done2; Done2: for (startY = rect.Y; startY < bitmapData.Height; startY++) for (int i = startX; i < endX; i++) if (!emptyPix(bitmapData, i, startY)) goto Done3; Done3: for (endY = rect.Y + rect.Height; endY >= 0; endY--) for (int i = startX; i < endX; i++) if (!emptyPix(bitmapData, i, endY)) goto Done4; Done4:; } if (endY < startY) startY = endY = rect.Y; if (endX < startX) startX = endX = rect.X; glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1); if (setYOffset) glyph.yOffset = glyph.rect.Y; }
public static void CreateBitmapPerGlyph(QFontGlyph[] sourceGlyphs, QBitmap[] sourceBitmaps, out QFontGlyph[] destGlyphs, out QBitmap[] destBitmaps){ destBitmaps = new QBitmap[sourceGlyphs.Length]; destGlyphs = new QFontGlyph[sourceGlyphs.Length]; for(int i = 0; i < sourceGlyphs.Length; i++){ var sg = sourceGlyphs[i]; destGlyphs[i] = new QFontGlyph(i,new Rectangle(0,0,sg.rect.Width,sg.rect.Height),sg.yOffset,sg.character); destBitmaps[i] = new QBitmap(new Bitmap(sg.rect.Width,sg.rect.Height,PixelFormat.Format32bppArgb)); QBitmap.Blit(sourceBitmaps[sg.page].bitmapData,destBitmaps[i].bitmapData,sg.rect,0,0); } }
public static void CreateBitmapPerGlyph(QFontGlyph[] sourceGlyphs, QBitmap[] sourceBitmaps, out QFontGlyph[] destGlyphs, out QBitmap[] destBitmaps) { destBitmaps = new QBitmap[sourceGlyphs.Length]; destGlyphs = new QFontGlyph[sourceGlyphs.Length]; for(int i = 0; i < sourceGlyphs.Length; i++){ var sg = sourceGlyphs[i]; destGlyphs[i] = new QFontGlyph(i,new Rectangle(0,0,sg.rect.Width,sg.rect.Height),sg.yOffset,sg.character); destBitmaps[i] = new QBitmap(new Bitmap(sg.rect.Width,sg.rect.Height,PixelFormat.Format32bppArgb)); QBitmap.Blit(sourceBitmaps[sg.page].BitmapData,destBitmaps[i].BitmapData,sg.rect,0,0); } }
public QFont Load(string path, float downSampleFactor, QFontLoaderConfiguration loaderConfig) { if (loaderConfig == null) loaderConfig = new QFontLoaderConfiguration(); TransformViewport? transToVp = null; float fontScale = 1f; if (loaderConfig.TransformToCurrentOrthogProjection) transToVp = QFont.OrthogonalTransform(out fontScale); QFont qfont = new QFont(); qfont.fontData = new QFontData (); QFontDataInformation fontInfo = null; using (var fs = File.OpenRead (path)) { fontInfo = qfont.fontData.LoadFromStream (fs); } var bitmapFiles = fontInfo.GenerateBitmapPageNames (path); var bitmapPages = new List<QBitmap> (); foreach (var bitmapFileName in bitmapFiles) { // TODO : GDI+ require the bitmap files to be locked as indexed image // during the lifetime i.e. maybe reloaded from disk using (var fs = File.OpenRead (bitmapFileName)) using (var b = new Bitmap(fs)) { var qb = new QBitmap (bitmapFileName); bitmapPages.Add (qb); } } var glyphList = qfont.fontData.InitialiseQFontData (fontInfo, ref bitmapPages, downSampleFactor, loaderConfig); if (loaderConfig.ShadowConfig != null) { qfont.DropShadow = Helper.BuildDropShadow<QFont, QBitmap> ( bitmapPages, glyphList.ToArray (), loaderConfig.ShadowConfig, Helper.ToArray (fontInfo.CharSet), loaderConfig.KerningConfig.alphaEmptyPixelTolerance); } qfont.fontData.InitialiseKerningPairs (fontInfo, bitmapPages, glyphList, loaderConfig); if (loaderConfig.ShadowConfig != null) qfont.Options.DropShadowActive = true; if (transToVp != null) qfont.Options.TransformToViewport = transToVp; return qfont; }
public TexturePage(string filePath) { var bitmap = new QBitmap(filePath); CreateTexture(bitmap.bitmapData); bitmap.Free(); }
private static List<QBitmap> GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin, bool usePowerOfTwo) { var pages = new List<QBitmap>(); destGlyphs = new QFontGlyph[sourceGlyphs.Length]; QBitmap currentPage = null; int maxY = 0; foreach (var glph in sourceGlyphs) maxY = Math.Max(glph.rect.Height, maxY); int finalPageIndex = 0; int finalPageRequiredWidth = 0; int finalPageRequiredHeight = 0; for (int k = 0; k < 2; k++) { bool pre = k == 0; //first iteration is simply to determine the required size of the final page, so that we can crop it in advance int xPos = 0; int yPos = 0; int maxYInRow = 0; int totalTries = 0; for (int i = 0; i < sourceGlyphs.Length; i++) { if(!pre && currentPage == null){ if (finalPageIndex == pages.Count) { int width = Math.Min(destSheetWidth, usePowerOfTwo ? PowerOfTwo(finalPageRequiredWidth) : finalPageRequiredWidth); int height = Math.Min(destSheetHeight, usePowerOfTwo ? PowerOfTwo(finalPageRequiredHeight) : finalPageRequiredHeight); currentPage = new QBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } else { currentPage = new QBitmap(new Bitmap(destSheetWidth, destSheetHeight, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } pages.Add(currentPage); } totalTries++; if (totalTries > 10 * sourceGlyphs.Length) throw new Exception("Failed to fit font into texture pages"); var rect = sourceGlyphs[i].rect; if (xPos + rect.Width + 2 * destMargin <= destSheetWidth && yPos + rect.Height + 2 * destMargin <= destSheetHeight) { if (!pre) { //add to page if(sourceBitmaps[sourceGlyphs[i].page].PixelFormat == PixelFormat.Format32bppArgb) QBitmap.Blit(sourceBitmaps[sourceGlyphs[i].page], currentPage.BitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); else QBitmap.BlitMask(sourceBitmaps[sourceGlyphs[i].page], currentPage.BitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); destGlyphs[i] = new QFontGlyph(pages.Count - 1, new Rectangle(xPos + destMargin, yPos + destMargin, rect.Width, rect.Height), sourceGlyphs[i].yOffset, sourceGlyphs[i].character); } else { finalPageRequiredWidth = Math.Max(finalPageRequiredWidth, xPos + rect.Width + 2 * destMargin); finalPageRequiredHeight = Math.Max(finalPageRequiredHeight, yPos + rect.Height + 2 * destMargin); } xPos += rect.Width + 2 * destMargin; maxYInRow = Math.Max(maxYInRow, rect.Height); continue; } if (xPos + rect.Width + 2 * destMargin > destSheetWidth) { i--; yPos += maxYInRow + 2 * destMargin; xPos = 0; if (yPos + maxY + 2 * destMargin > destSheetHeight) { yPos = 0; if (!pre) { currentPage = null; } else { finalPageRequiredWidth = 0; finalPageRequiredHeight = 0; finalPageIndex++; } } continue; } } } return pages; }
/// <summary> /// Builds the font data /// </summary> /// <param name="saveName">The filename to save the font texture files too. If null, the texture files are not saved</param> /// <returns>A <see cref="QFontData"/></returns> public QFontData BuildFontData(string saveName = null) { // Check super sample level range if (_config.SuperSampleLevels <= 0 || _config.SuperSampleLevels > 8) { throw new ArgumentOutOfRangeException("SuperSampleLevels = [" + _config.SuperSampleLevels + "] is an unsupported value. Please use values in the range [1,8]"); } int margin = 3; //margin in initial bitmap (don't bother to make configurable - likely to cause confusion int glyphMargin = _config.GlyphMargin * _config.SuperSampleLevels; QFontGlyph[] initialGlyphs; var sizes = GetGlyphSizes(_font); var maxSize = GetMaxGlyphSize(sizes); var initialBmp = CreateInitialBitmap(_font, maxSize, margin, out initialGlyphs, _config.TextGenerationRenderHint); #if DEBUG // print bitmap with bounds to debug it var debugBmp = initialBmp.Clone() as Bitmap; var graphics = Graphics.FromImage(debugBmp); var pen = new Pen(Color.Red, 1); foreach (var g in initialGlyphs) { graphics.DrawRectangle(pen, g.Rect); } graphics.Flush(); graphics.Dispose(); debugBmp.Save(_font + "-DEBUG.png", ImageFormat.Png); #endif var initialBitmapData = initialBmp.LockBits(new Rectangle(0, 0, initialBmp.Width, initialBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int minYOffset = int.MaxValue; // Retarget each glyph rectangle to get minimum bounding box foreach (var glyph in initialGlyphs) { RetargetGlyphRectangleInwards(initialBitmapData, glyph, true, _config.KerningConfig.AlphaEmptyPixelTolerance); minYOffset = Math.Min(minYOffset, glyph.YOffset); } minYOffset--; //give one pixel of breathing room? // Update glyph y offsets foreach (var glyph in initialGlyphs) { glyph.YOffset -= minYOffset; } // Find the optimal page size for the font Size pagesize = GetOptimalPageSize(initialBmp.Width * _config.SuperSampleLevels, initialBmp.Height * _config.SuperSampleLevels, _config.PageMaxTextureSize); QFontGlyph[] glyphs; // Generate the final bitmap pages List <QBitmap> bitmapPages = GenerateBitmapSheetsAndRepack(initialGlyphs, new[] { initialBitmapData }, pagesize.Width, pagesize.Height, out glyphs, glyphMargin); // Clean up initialBmp.UnlockBits(initialBitmapData); initialBmp.Dispose(); // Scale and retarget glyphs if needed if (_config.SuperSampleLevels != 1) { ScaleSheetsAndGlyphs(bitmapPages, glyphs, 1.0f / _config.SuperSampleLevels); RetargetAllGlyphs(bitmapPages, glyphs, _config.KerningConfig.AlphaEmptyPixelTolerance); } //create list of texture pages var pages = new List <TexturePage>(); foreach (var page in bitmapPages) { pages.Add(new TexturePage(page.BitmapData)); } // Build the QFontData var fontData = new QFontData { CharSetMapping = CreateCharGlyphMapping(glyphs), Pages = pages.ToArray() }; fontData.CalculateMeanWidth(); fontData.CalculateMaxHeight(); fontData.KerningPairs = KerningCalculator.CalculateKerning(_charSet.ToCharArray(), glyphs, bitmapPages, _config.KerningConfig, _font); fontData.NaturallyMonospaced = IsMonospaced(sizes); // Save the font texture files if required if (saveName != null) { if (bitmapPages.Count == 1) { bitmapPages[0].Bitmap.UnlockBits(bitmapPages[0].BitmapData); bitmapPages[0].Bitmap.Save(saveName + ".png", ImageFormat.Png); bitmapPages[0] = new QBitmap(bitmapPages[0].Bitmap); } else { for (int i = 0; i < bitmapPages.Count; i++) { bitmapPages[i].Bitmap.UnlockBits(bitmapPages[i].BitmapData); bitmapPages[i].Bitmap.Save(saveName + "_sheet_" + i + ".png", ImageFormat.Png); bitmapPages[i] = new QBitmap(bitmapPages[i].Bitmap); } } } // Build the font drop shadow if required if (_config.ShadowConfig != null) { fontData.DropShadowFont = BuildDropShadow(bitmapPages, glyphs, _config.ShadowConfig, _charSet.ToCharArray(), _config.KerningConfig.AlphaEmptyPixelTolerance); } // Clean up resources foreach (var page in bitmapPages) { page.Free(); } // Check that no glyphs are overlapping var intercept = FirstIntercept(fontData.CharSetMapping); if (intercept != null) { throw new Exception("Failed to create glyph set. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. This is could be due to an error in the font, or a bug in Graphics.MeasureString()."); } return(fontData); }
/// <summary> /// Calculates the kerning values for the given character set /// </summary> /// <param name="charSet">The character set to calculate kerning values for</param> /// <param name="glyphs">The glyphs used for kerning</param> /// <param name="bitmapPages">The bitmap pages of the glyphs</param> /// <param name="config">The kerning configuration</param> /// <param name="font">The <see cref="IFont"/> used to create the glyphs and bitmaps</param> /// <returns>A <see cref="Dictionary{TKey,TValue}"/> mapping of every glyph pair to a kerning amount</returns> public static Dictionary <string, int> CalculateKerning(char[] charSet, QFontGlyph[] glyphs, List <QBitmap> bitmapPages, QFontKerningConfiguration config, IFont font = null) { var kerningPairs = new Dictionary <string, int>(); //we start by computing the index of the first and last non-empty pixel in each row of each glyph XLimits[][] limits = new XLimits[charSet.Length][]; int maxHeight = 0; for (int n = 0; n < charSet.Length; n++) { var rect = glyphs[n].Rect; var page = bitmapPages[glyphs[n].Page]; limits[n] = new XLimits[rect.Height + 1]; maxHeight = Math.Max(rect.Height, maxHeight); int yStart = rect.Y; int yEnd = rect.Y + rect.Height; int xStart = rect.X; int xEnd = rect.X + rect.Width; for (int j = yStart; j <= yEnd; j++) { int last = xStart; bool yetToFindFirst = true; for (int i = xStart; i <= xEnd; i++) { if (!QBitmap.EmptyAlphaPixel(page.BitmapData, i, j, config.AlphaEmptyPixelTolerance)) { if (yetToFindFirst) { limits[n][j - yStart].Min = i - xStart; yetToFindFirst = false; } last = i; } } limits[n][j - yStart].Max = last - xStart; if (yetToFindFirst) { limits[n][j - yStart].Min = xEnd - 1; } } } //we now bring up each row to the max (or min) of it's two adjacent rows, this is to stop glyphs sliding together too closely var tmp = new XLimits[maxHeight + 1]; for (int n = 0; n < charSet.Length; n++) { //clear tmp for (int j = 0; j < limits[n].Length; j++) { tmp[j] = limits[n][j]; } for (int j = 0; j < limits[n].Length; j++) { if (j != 0) { tmp[j].Min = Math.Min(limits[n][j - 1].Min, tmp[j].Min); tmp[j].Max = Math.Max(limits[n][j - 1].Max, tmp[j].Max); } if (j != limits[n].Length - 1) { tmp[j].Min = Math.Min(limits[n][j + 1].Min, tmp[j].Min); tmp[j].Max = Math.Max(limits[n][j + 1].Max, tmp[j].Max); } } for (int j = 0; j < limits[n].Length; j++) { limits[n][j] = tmp[j]; } } // For each character in the character set, // combine it with every other character and add it to the kerning pair dictionary for (int i = 0; i < charSet.Length; i++) { for (int j = 0; j < charSet.Length; j++) { kerningPairs.Add("" + charSet[i] + charSet[j], Kerning(glyphs[i], glyphs[j], limits[i], limits[j], config, font)); } } return(kerningPairs); }
public QFontData BuildFontData(string saveName) { if (config.SuperSampleLevels <= 0 || config.SuperSampleLevels > 8) { throw new ArgumentOutOfRangeException("SuperSampleLevels = [" + config.SuperSampleLevels + "] is an unsupported value. Please use values in the range [1,8]"); } int margin = 3; //margin in initial bitmap (don't bother to make configurable - likely to cause confusion int glyphMargin = config.GlyphMargin * config.SuperSampleLevels; QFontGlyph[] initialGlyphs; var sizes = GetGlyphSizes(font); var maxSize = GetMaxGlyphSize(sizes); var initialBmp = CreateInitialBitmap(font, maxSize, margin, out initialGlyphs, config.TextGenerationRenderHint); var initialBitmapData = initialBmp.LockBits(new Rectangle(0, 0, initialBmp.Width, initialBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int minYOffset = int.MaxValue; foreach (var glyph in initialGlyphs) { RetargetGlyphRectangleInwards(initialBitmapData, glyph, true, config.KerningConfig.alphaEmptyPixelTolerance); minYOffset = Math.Min(minYOffset, glyph.yOffset); } minYOffset--; //give one pixel of breathing room? foreach (var glyph in initialGlyphs) { glyph.yOffset -= minYOffset; } Size pagesize = GetOptimalPageSize(initialBmp.Width * config.SuperSampleLevels, initialBmp.Height * config.SuperSampleLevels, config.PageMaxTextureSize); QFontGlyph[] glyphs; List <QBitmap> bitmapPages = GenerateBitmapSheetsAndRepack(initialGlyphs, new BitmapData[1] { initialBitmapData }, pagesize.Width, pagesize.Height, out glyphs, glyphMargin); initialBmp.UnlockBits(initialBitmapData); initialBmp.Dispose(); if (config.SuperSampleLevels != 1) { ScaleSheetsAndGlyphs(bitmapPages, glyphs, 1.0f / config.SuperSampleLevels); RetargetAllGlyphs(bitmapPages, glyphs, config.KerningConfig.alphaEmptyPixelTolerance); } //create list of texture pages var pages = new List <TexturePage>(); foreach (var page in bitmapPages) { pages.Add(new TexturePage(page.bitmapData)); } var fontData = new QFontData(); fontData.CharSetMapping = CreateCharGlyphMapping(glyphs); fontData.Pages = pages.ToArray(); fontData.CalculateMeanWidth(); fontData.CalculateMaxHeight(); fontData.KerningPairs = KerningCalculator.CalculateKerning(charSet.ToCharArray(), glyphs, bitmapPages, config.KerningConfig); fontData.naturallyMonospaced = IsMonospaced(sizes); if (saveName != null) { if (bitmapPages.Count == 1) { bitmapPages[0].bitmap.UnlockBits(bitmapPages[0].bitmapData); bitmapPages[0].bitmap.Save(saveName + ".png", ImageFormat.Png); bitmapPages[0] = new QBitmap(bitmapPages[0].bitmap); } else { for (int i = 0; i < bitmapPages.Count; i++) { bitmapPages[i].bitmap.UnlockBits(bitmapPages[i].bitmapData); bitmapPages[i].bitmap.Save(saveName + "_sheet_" + i + ".png", ImageFormat.Png); bitmapPages[i] = new QBitmap(bitmapPages[i].bitmap); } } } if (config.ShadowConfig != null) { fontData.dropShadowFont = BuildDropShadow(bitmapPages, glyphs, config.ShadowConfig, charSet.ToCharArray(), config.KerningConfig.alphaEmptyPixelTolerance); } foreach (var page in bitmapPages) { page.Free(); } //validate glyphs var intercept = FirstIntercept(fontData.CharSetMapping); if (intercept != null) { throw new Exception("Failed to create glyph set. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. This is could be due to an error in the font, or a bug in Graphics.MeasureString()."); } return(fontData); }
private static void RetargetGlyphRectangleOutwards(BitmapData bitmapData, QFontGlyph glyph, bool setYOffset, byte alphaTolerance) { int startX, endX; int startY, endY; var rect = glyph.rect; EmptyDel emptyPix; if (bitmapData.PixelFormat == PixelFormat.Format32bppArgb) { emptyPix = delegate(BitmapData data, int x, int y) { return(QBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance)); } } ; else { emptyPix = delegate(BitmapData data, int x, int y) { return(QBitmap.EmptyPixel(data, x, y)); } }; unsafe { for (startX = rect.X; startX >= 0; startX--) { bool foundPix = false; for (int j = rect.Y; j <= rect.Y + rect.Height; j++) { if (!emptyPix(bitmapData, startX, j)) { foundPix = true; break; } } if (!foundPix) { startX++; break; } } for (endX = rect.X + rect.Width; endX < bitmapData.Width; endX++) { bool foundPix = false; for (int j = rect.Y; j <= rect.Y + rect.Height; j++) { if (!emptyPix(bitmapData, endX, j)) { foundPix = true; break; } } if (!foundPix) { endX--; break; } } for (startY = rect.Y; startY >= 0; startY--) { bool foundPix = false; for (int i = startX; i <= endX; i++) { if (!emptyPix(bitmapData, i, startY)) { foundPix = true; break; } } if (!foundPix) { startY++; break; } } for (endY = rect.Y + rect.Height; endY < bitmapData.Height; endY++) { bool foundPix = false; for (int i = startX; i <= endX; i++) { if (!emptyPix(bitmapData, i, endY)) { foundPix = true; break; } } if (!foundPix) { endY--; break; } } } glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1); if (setYOffset) { glyph.yOffset = glyph.rect.Y; } }
private void Form1_Paint(object _sender, PaintEventArgs _e) { m_allEdges.Clear(); for (var i = 0; i < Map.SIZE; ++i) { for (var j = 0; j < Map.SIZE; ++j) { if (m_map[i, j] > 0) { var rectEdges = GetRectEdges(new Rectangle((int)(i * SZ), (int)(j * SZ), (int)SZ, (int)SZ), m_map[i, j]); if (j > 0 && m_map[i, j - 1] == 0) { m_allEdges.Add(rectEdges[0]); } if (i < (Map.SIZE - 1) && m_map[i + 1, j] == 0) { m_allEdges.Add(rectEdges[1]); } if (j < (Map.SIZE - 1) && m_map[i, j + 1] == 0) { m_allEdges.Add(rectEdges[2]); } if (i > 0 && m_map[i - 1, j] == 0) { m_allEdges.Add(rectEdges[3]); } } } } #region зона видимости using (var g = Graphics.FromImage(m_buf3)) { g.SmoothingMode = SmoothingMode.AntiAlias; g.Clear(Color.Empty); Draw(_e, new PointF(Map.SIZE * SZ / 2 , Map.SIZE * SZ / 2 ), Color.White, 0, g); } #endregion using (var g = Graphics.FromImage(m_buf1)) { //g.CompositingQuality = CompositingQuality.GammaCorrected; g.SmoothingMode = SmoothingMode.AntiAlias; g.Clear(Color.Empty); Draw(_e, new PointF(_mousePnt.X * SZ, _mousePnt.Y * SZ), Color.FromArgb(255, 255, 0, 0), 0, g); } using (var g = Graphics.FromImage(m_buf2)) { //g.CompositingQuality = CompositingQuality.GammaCorrected; g.SmoothingMode = SmoothingMode.AntiAlias; g.Clear(Color.Empty); Draw(_e, new PointF(_mousePnt.Y * SZ, _mousePnt.X * SZ), Color.FromArgb(255, 0, 0, 255), 0, g); } using (var g = Graphics.FromImage(m_buf0)) { //g.CompositingQuality = CompositingQuality.GammaCorrected; g.SmoothingMode = SmoothingMode.AntiAlias; g.Clear(Color.Empty); Draw(_e, new PointF(_mousePnt.Y * SZ, _mousePnt.Y * SZ), Color.FromArgb(255, 0, 255, 0), 0, g); } //DrawWalls(m_buf2); using (var t= new QBitmap(m_buf3)) { t.ApplyLightMaps(new[] { m_buf0, m_buf1, m_buf2 }); } //_e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; _e.Graphics.CompositingMode = CompositingMode.SourceCopy; //_e.Graphics.InterpolationMode = InterpolationMode.Bilinear; _e.Graphics.DrawImage(m_buf3, new RectangleF(0, 0, Map.SIZE , Map.SIZE ), new RectangleF(0, 0, Map.SIZE * SZ, Map.SIZE * SZ), GraphicsUnit.Pixel); //Draw(_e, new PointF(_mousePnt.Y - m_offset.Y, _mousePnt.X - m_offset.X), Color.FromArgb(180, 255, 0), Map.SIZE); //Draw(_e, new PointF(_mousePnt.X - m_offset.X, _mousePnt.Y - m_offset.Y), Color.FromArgb(255, 180, 0), Map.SIZE * 2); }
/* public void Blur(int radius, int passes) { QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); byte r=0,g=0,b=0,a=0; int summedR, summedG, summedB, summedA; int weight = 0; int xpos, ypos, x, y, kx, ky; for (int pass = 0; pass < passes; pass++) { //horizontal pass for (y = 0; y < bitmap.Height; y++) { for (x = 0; x < bitmap.Width; x++) { summedR = summedG = summedB = summedA = weight = 0; for (kx = -radius; kx <= radius; kx++) { xpos = x + kx; if (xpos >= 0 && xpos < bitmap.Width) { GetPixel32(xpos, y, ref r, ref g, ref b, ref a); summedR += r; summedG += g; summedB += b; summedA += a; weight++; } } summedR /= weight; summedG /= weight; summedB /= weight; summedA /= weight; tmp.PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); } } //vertical pass for (x = 0; x < bitmap.Width; ++x) { for (y = 0; y < bitmap.Height; ++y) { summedR = summedG = summedB = summedA = weight = 0; for (ky = -radius; ky <= radius; ky++) { ypos = y + ky; if (ypos >= 0 && ypos < bitmap.Height) { tmp.GetPixel32(x, ypos, ref r, ref g, ref b, ref a); summedR += r; summedG += g; summedB += b; summedA += a; weight++; } } summedR /= weight; summedG /= weight; summedB /= weight; summedA /= weight; PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); } } } tmp.Free(); }*/ public void ExpandAlpha(int radius, int passes) { QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); byte a = 0; byte max; int xpos, ypos, x, y, kx, ky; int width = bitmap.Width; int height = bitmap.Height; for (int pass = 0; pass < passes; pass++) { //horizontal pass for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { max = 0; for (kx = -radius; kx <= radius; kx++) { xpos = x + kx; if (xpos >= 0 && xpos < width) { GetAlpha32(xpos, y, ref a); if (a > max) max = a; } } tmp.PutAlpha32(x, y, max); } } //vertical pass for (x = 0; x < width; ++x) { for (y = 0; y < height; ++y) { max = 0; for (ky = -radius; ky <= radius; ky++) { ypos = y + ky; if (ypos >= 0 && ypos < height) { tmp.GetAlpha32(x, ypos, ref a); if (a > max) max = a; } } PutAlpha32(x, y, max); } } } tmp.Free(); }
public void ApplyLightMaps(Bitmap[] _lightmaps) { unsafe { var lightQMaps = new QBitmap[_lightmaps.Length]; var ptrs = new byte *[_lightmaps.Length]; for (var index = 0; index < _lightmaps.Length; index++) { lightQMaps[index] = new QBitmap(_lightmaps[index]); ptrs[index] = (byte *)(lightQMaps[index].m_bitmapData.Scan0); } var sourcePtr = (byte *)(m_bitmapData.Scan0); byte r, g, b; byte vr, vg, vb; for (var i = 0; i < m_bitmapData.Height; i++) { for (var j = 0; j < m_bitmapData.Width; j++) { vr = *(sourcePtr + 2); vg = *(sourcePtr + 1); vb = *(sourcePtr + 0); r = 0; g = 0; b = 0; for (var index = 0; index < _lightmaps.Length; index++) { r = Math.Max(r, *(ptrs[index] + 2)); g = Math.Max(g, *(ptrs[index] + 1)); b = Math.Max(b, *(ptrs[index] + 0)); ptrs[index] += 4; } r = (byte)(r * vr / 255); g = (byte)(g * vg / 255); b = (byte)(b * vb / 255); //if (vr > 0 && r>0) //{ // vr += 1; //} *(sourcePtr) = b; *(sourcePtr + 1) = g; *(sourcePtr + 2) = r; //*(sourcePtr + 3) = _a; sourcePtr += 4; } sourcePtr += m_bitmapData.Stride - m_bitmapData.Width * 4; //move to the end of the line (past unused space) for (var index = 0; index < _lightmaps.Length; index++) { ptrs[index] += m_bitmapData.Stride - m_bitmapData.Width * 4; //move to the end of the line (past unused space) } } for (var index = 0; index < _lightmaps.Length; index++) { lightQMaps[index].Dispose(); } } }
public void ApplyLightMaps(Bitmap[] _lightmaps) { unsafe { var lightQMaps = new QBitmap[_lightmaps.Length]; var ptrs = new byte*[_lightmaps.Length]; for (var index = 0; index < _lightmaps.Length; index++) { lightQMaps[index] = new QBitmap(_lightmaps[index]); ptrs[index] = (byte*)(lightQMaps[index].m_bitmapData.Scan0); } var sourcePtr = (byte*)(m_bitmapData.Scan0); byte r, g, b; byte vr, vg, vb; for (var i = 0; i < m_bitmapData.Height; i++) { for (var j = 0; j < m_bitmapData.Width; j++) { vr = *(sourcePtr + 2); vg = *(sourcePtr + 1); vb = *(sourcePtr + 0); r = 0; g = 0; b = 0; for (var index = 0; index < _lightmaps.Length; index++) { r = Math.Max(r, *(ptrs[index] + 2)); g = Math.Max(g,*(ptrs[index] + 1)); b = Math.Max(b,*(ptrs[index] + 0)); ptrs[index] += 4; } r = (byte)(r * vr / 255); g = (byte)(g*vg/255); b = (byte)(b*vb/255); //if (vr > 0 && r>0) //{ // vr += 1; //} *(sourcePtr) = b; *(sourcePtr + 1) = g; *(sourcePtr + 2) = r; //*(sourcePtr + 3) = _a; sourcePtr += 4; } sourcePtr += m_bitmapData.Stride - m_bitmapData.Width * 4; //move to the end of the line (past unused space) for (var index = 0; index < _lightmaps.Length; index++) { ptrs[index] += m_bitmapData.Stride - m_bitmapData.Width * 4; //move to the end of the line (past unused space) } } for (var index = 0; index < _lightmaps.Length; index++) { lightQMaps[index].Dispose(); } } }
public void DownScale32(int newWidth, int newHeight) { QBitmap newBitmap = new QBitmap(new Bitmap(newWidth, newHeight, bitmap.PixelFormat)); if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) { throw new Exception("DownsScale32 only works on 32 bit images"); } float xscale = (float)bitmapData.Width / newWidth; float yscale = (float)bitmapData.Height / newHeight; byte r = 0, g = 0, b = 0, a = 0; float summedR = 0f; float summedG = 0f; float summedB = 0f; float summedA = 0f; int left, right, top, bottom; //the area of old pixels covered by the new bitmap float targetStartX, targetEndX; float targetStartY, targetEndY; float leftF, rightF, topF, bottomF; //edges of new pixel in old pixel coords float weight; float weightScale = xscale * yscale; float totalColourWeight = 0f; for (int m = 0; m < newHeight; m++) { for (int n = 0; n < newWidth; n++) { leftF = n * xscale; rightF = (n + 1) * xscale; topF = m * yscale; bottomF = (m + 1) * yscale; left = (int)leftF; right = (int)rightF; top = (int)topF; bottom = (int)bottomF; if (left < 0) { left = 0; } if (top < 0) { top = 0; } if (right >= bitmapData.Width) { right = bitmapData.Width - 1; } if (bottom >= bitmapData.Height) { bottom = bitmapData.Height - 1; } summedR = 0f; summedG = 0f; summedB = 0f; summedA = 0f; totalColourWeight = 0f; for (int j = top; j <= bottom; j++) { for (int i = left; i <= right; i++) { targetStartX = Math.Max(leftF, i); targetEndX = Math.Min(rightF, i + 1); targetStartY = Math.Max(topF, j); targetEndY = Math.Min(bottomF, j + 1); weight = (targetEndX - targetStartX) * (targetEndY - targetStartY); GetPixel32(i, j, ref r, ref g, ref b, ref a); summedA += weight * a; if (a != 0) { summedR += weight * r; summedG += weight * g; summedB += weight * b; totalColourWeight += weight; } } } summedR /= totalColourWeight; summedG /= totalColourWeight; summedB /= totalColourWeight; summedA /= weightScale; if (summedR < 0) { summedR = 0f; } if (summedG < 0) { summedG = 0f; } if (summedB < 0) { summedB = 0f; } if (summedA < 0) { summedA = 0f; } if (summedR >= 256) { summedR = 255; } if (summedG >= 256) { summedG = 255; } if (summedB >= 256) { summedB = 255; } if (summedA >= 256) { summedA = 255; } newBitmap.PutPixel32(n, m, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); } } this.Free(); this.bitmap = newBitmap.bitmap; this.bitmapData = newBitmap.bitmapData; }
/* * * public void Blur(int radius, int passes) * { * QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); * * byte r=0,g=0,b=0,a=0; * int summedR, summedG, summedB, summedA; * int weight = 0; * int xpos, ypos, x, y, kx, ky; * * * for (int pass = 0; pass < passes; pass++) * { * * //horizontal pass * for (y = 0; y < bitmap.Height; y++) * { * for (x = 0; x < bitmap.Width; x++) * { * summedR = summedG = summedB = summedA = weight = 0; * for (kx = -radius; kx <= radius; kx++) * { * xpos = x + kx; * if (xpos >= 0 && xpos < bitmap.Width) * { * GetPixel32(xpos, y, ref r, ref g, ref b, ref a); * * * summedR += r; * summedG += g; * summedB += b; * summedA += a; * weight++; * } * * } * * summedR /= weight; * summedG /= weight; * summedB /= weight; * summedA /= weight; * * tmp.PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); * } * } * * * * * //vertical pass * for (x = 0; x < bitmap.Width; ++x) * { * for (y = 0; y < bitmap.Height; ++y) * { * summedR = summedG = summedB = summedA = weight = 0; * for (ky = -radius; ky <= radius; ky++) * { * ypos = y + ky; * if (ypos >= 0 && ypos < bitmap.Height) * { * tmp.GetPixel32(x, ypos, ref r, ref g, ref b, ref a); * * summedR += r; * summedG += g; * summedB += b; * summedA += a; * weight++; * } * } * * summedR /= weight; * summedG /= weight; * summedB /= weight; * summedA /= weight; * * PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); * * } * } * * } * * tmp.Free(); * * }*/ public void BlurAlpha(int radius, int passes) { QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); byte a = 0; int summedA; int weight = 0; int xpos, ypos, x, y, kx, ky; int width = bitmap.Width; int height = bitmap.Height; for (int pass = 0; pass < passes; pass++) { //horizontal pass for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { summedA = weight = 0; for (kx = -radius; kx <= radius; kx++) { xpos = x + kx; if (xpos >= 0 && xpos < width) { GetAlpha32(xpos, y, ref a); summedA += a; weight++; } } summedA /= weight; tmp.PutAlpha32(x, y, (byte)summedA); } } //vertical pass for (x = 0; x < width; ++x) { for (y = 0; y < height; ++y) { summedA = weight = 0; for (ky = -radius; ky <= radius; ky++) { ypos = y + ky; if (ypos >= 0 && ypos < height) { tmp.GetAlpha32(x, ypos, ref a); summedA += a; weight++; } } summedA /= weight; PutAlpha32(x, y, (byte)summedA); } } } tmp.Free(); }
/* * * public void Blur(int radius, int passes) * { * QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); * * byte r=0,g=0,b=0,a=0; * int summedR, summedG, summedB, summedA; * int weight = 0; * int xpos, ypos, x, y, kx, ky; * * * for (int pass = 0; pass < passes; pass++) * { * * //horizontal pass * for (y = 0; y < bitmap.Height; y++) * { * for (x = 0; x < bitmap.Width; x++) * { * summedR = summedG = summedB = summedA = weight = 0; * for (kx = -radius; kx <= radius; kx++) * { * xpos = x + kx; * if (xpos >= 0 && xpos < bitmap.Width) * { * GetPixel32(xpos, y, ref r, ref g, ref b, ref a); * * * summedR += r; * summedG += g; * summedB += b; * summedA += a; * weight++; * } * * } * * summedR /= weight; * summedG /= weight; * summedB /= weight; * summedA /= weight; * * tmp.PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); * } * } * * * * * //vertical pass * for (x = 0; x < bitmap.Width; ++x) * { * for (y = 0; y < bitmap.Height; ++y) * { * summedR = summedG = summedB = summedA = weight = 0; * for (ky = -radius; ky <= radius; ky++) * { * ypos = y + ky; * if (ypos >= 0 && ypos < bitmap.Height) * { * tmp.GetPixel32(x, ypos, ref r, ref g, ref b, ref a); * * summedR += r; * summedG += g; * summedB += b; * summedA += a; * weight++; * } * } * * summedR /= weight; * summedG /= weight; * summedB /= weight; * summedA /= weight; * * PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); * * } * } * * } * * tmp.Free(); * * }*/ public void ExpandAlpha(int radius, int passes) { QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); byte a = 0; byte max; int xpos, ypos, x, y, kx, ky; int width = bitmap.Width; int height = bitmap.Height; for (int pass = 0; pass < passes; pass++) { //horizontal pass for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { max = 0; for (kx = -radius; kx <= radius; kx++) { xpos = x + kx; if (xpos >= 0 && xpos < width) { GetAlpha32(xpos, y, ref a); if (a > max) { max = a; } } } tmp.PutAlpha32(x, y, max); } } //vertical pass for (x = 0; x < width; ++x) { for (y = 0; y < height; ++y) { max = 0; for (ky = -radius; ky <= radius; ky++) { ypos = y + ky; if (ypos >= 0 && ypos < height) { tmp.GetAlpha32(x, ypos, ref a); if (a > max) { max = a; } } } PutAlpha32(x, y, max); } } } tmp.Free(); }
private static List <QBitmap> GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin) { var pages = new List <QBitmap>(); destGlyphs = new QFontGlyph[sourceGlyphs.Length]; QBitmap currentPage = null; int maxY = 0; foreach (var glph in sourceGlyphs) { maxY = Math.Max(glph.rect.Height, maxY); } int finalPageIndex = 0; int finalPageRequiredWidth = 0; int finalPageRequiredHeight = 0; for (int k = 0; k < 2; k++) { bool pre = k == 0; //first iteration is simply to determine the required size of the final page, so that we can crop it in advance int xPos = 0; int yPos = 0; int maxYInRow = 0; int totalTries = 0; for (int i = 0; i < sourceGlyphs.Length; i++) { if (!pre && currentPage == null) { if (finalPageIndex == pages.Count) { int width = Math.Min(destSheetWidth, finalPageRequiredWidth); int height = Math.Min(destSheetHeight, finalPageRequiredHeight); currentPage = new QBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } else { currentPage = new QBitmap(new Bitmap(destSheetWidth, destSheetHeight, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } pages.Add(currentPage); } totalTries++; if (totalTries > 10 * sourceGlyphs.Length) { throw new Exception("Failed to fit font into texture pages"); } var rect = sourceGlyphs[i].rect; if (xPos + rect.Width + 2 * destMargin <= destSheetWidth && yPos + rect.Height + 2 * destMargin <= destSheetHeight) { if (!pre) { //add to page if (sourceBitmaps[sourceGlyphs[i].page].PixelFormat == PixelFormat.Format32bppArgb) { QBitmap.Blit(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); } else { QBitmap.BlitMask(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); } destGlyphs[i] = new QFontGlyph(pages.Count - 1, new Rectangle(xPos + destMargin, yPos + destMargin, rect.Width, rect.Height), sourceGlyphs[i].yOffset, sourceGlyphs[i].character); } else { finalPageRequiredWidth = Math.Max(finalPageRequiredWidth, xPos + rect.Width + 2 * destMargin); finalPageRequiredHeight = Math.Max(finalPageRequiredHeight, yPos + rect.Height + 2 * destMargin); } xPos += rect.Width + 2 * destMargin; maxYInRow = Math.Max(maxYInRow, rect.Height); continue; } if (xPos + rect.Width + 2 * destMargin > destSheetWidth) { i--; yPos += maxYInRow + 2 * destMargin; xPos = 0; if (yPos + maxY + 2 * destMargin > destSheetHeight) { yPos = 0; if (!pre) { currentPage = null; } else { finalPageRequiredWidth = 0; finalPageRequiredHeight = 0; finalPageIndex++; } } continue; } } } return(pages); }
public void DownScale32(int newWidth, int newHeight) { QBitmap newBitmap = new QBitmap(new Bitmap(newWidth, newHeight, bitmap.PixelFormat)); if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) throw new Exception("DownsScale32 only works on 32 bit images"); float xscale = (float)bitmapData.Width / newWidth; float yscale = (float)bitmapData.Height / newHeight; byte r = 0, g = 0, b = 0, a = 0; float summedR = 0f; float summedG = 0f; float summedB = 0f; float summedA = 0f; int left, right, top, bottom; //the area of old pixels covered by the new bitmap float targetStartX, targetEndX; float targetStartY, targetEndY; float leftF, rightF, topF, bottomF; //edges of new pixel in old pixel coords float weight; float weightScale = xscale * yscale; float totalColourWeight = 0f; for (int m = 0; m < newHeight; m++) { for (int n = 0; n < newWidth; n++) { leftF = n * xscale; rightF = (n + 1) * xscale; topF = m * yscale; bottomF = (m + 1) * yscale; left = (int)leftF; right = (int)rightF; top = (int)topF; bottom = (int)bottomF; if (left < 0) left = 0; if (top < 0) top = 0; if (right >= bitmapData.Width) right = bitmapData.Width - 1; if (bottom >= bitmapData.Height) bottom = bitmapData.Height - 1; summedR = 0f; summedG = 0f; summedB = 0f; summedA = 0f; totalColourWeight = 0f; for (int j = top; j <= bottom; j++) { for (int i = left; i <= right; i++) { targetStartX = Math.Max(leftF, i); targetEndX = Math.Min(rightF, i + 1); targetStartY = Math.Max(topF, j); targetEndY = Math.Min(bottomF, j + 1); weight = (targetEndX - targetStartX) * (targetEndY - targetStartY); GetPixel32(i, j, ref r, ref g, ref b, ref a); summedA += weight * a; if (a != 0) { summedR += weight * r; summedG += weight * g; summedB += weight * b; totalColourWeight += weight; } } } summedR /= totalColourWeight; summedG /= totalColourWeight; summedB /= totalColourWeight; summedA /= weightScale; if (summedR < 0) summedR = 0f; if (summedG < 0) summedG = 0f; if (summedB < 0) summedB = 0f; if (summedA < 0) summedA = 0f; if (summedR >= 256) summedR = 255; if (summedG >= 256) summedG = 255; if (summedB >= 256) summedB = 255; if (summedA >= 256) summedA = 255; newBitmap.PutPixel32(n, m, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); } } this.Free(); this.bitmap = newBitmap.bitmap; this.bitmapData = newBitmap.bitmapData; }
public static Dictionary <String, int> CalculateKerning(char[] charSet, QFontGlyph[] glyphs, List <QBitmap> bitmapPages, QFontKerningConfiguration config) { var kerningPairs = new Dictionary <String, int>(); //we start by computing the index of the first and last non-empty pixel in each row of each glyph XLimits[][] limits = new XLimits[charSet.Length][]; int maxHeight = 0; for (int n = 0; n < charSet.Length; n++) { var rect = glyphs[n].rect; var page = bitmapPages[glyphs[n].page]; limits[n] = new XLimits[rect.Height]; maxHeight = Math.Max(rect.Height, maxHeight); int yStart = rect.Y; int yEnd = rect.Y + rect.Height; int xStart = rect.X; int xEnd = rect.X + rect.Width; for (int j = yStart; j < yEnd; j++) { int last = xStart; bool yetToFindFirst = true; for (int i = xStart; i < xEnd; i++) { if (!QBitmap.EmptyAlphaPixel(page.bitmapData, i, j, config.alphaEmptyPixelTolerance)) { if (yetToFindFirst) { limits[n][j - yStart].Min = i - xStart; yetToFindFirst = false; } last = i; } } limits[n][j - yStart].Max = last - xStart; if (yetToFindFirst) { limits[n][j - yStart].Min = xEnd - 1; } } } //we now bring up each row to the max (or min) of it's two adjacent rows, this is to stop glyphs sliding together too closely var tmp = new XLimits[maxHeight]; for (int n = 0; n < charSet.Length; n++) { //clear tmp for (int j = 0; j < limits[n].Length; j++) { tmp[j] = limits[n][j]; } for (int j = 0; j < limits[n].Length; j++) { if (j != 0) { tmp[j].Min = Math.Min(limits[n][j - 1].Min, tmp[j].Min); tmp[j].Max = Math.Max(limits[n][j - 1].Max, tmp[j].Max); } if (j != limits[n].Length - 1) { tmp[j].Min = Math.Min(limits[n][j + 1].Min, tmp[j].Min); tmp[j].Max = Math.Max(limits[n][j + 1].Max, tmp[j].Max); } } for (int j = 0; j < limits[n].Length; j++) { limits[n][j] = tmp[j]; } } //поправил сам if (config.KerningPairs == null) { for (int i = 0; i < charSet.Length; i++) { for (int j = 0; j < charSet.Length; j++) { kerningPairs.Add("" + charSet[i] + charSet[j], 1 - Kerning(glyphs[i], glyphs[j], limits[i], limits[j], config)); } } } else { var chatSetList = charSet.ToList(); var indices = config.KerningPairs.Select(x => Tuple.Create(chatSetList.IndexOf(x[0]), chatSetList.IndexOf(x[1]))).ToList(); foreach (var pair in indices) { int i = pair.Item1; int j = pair.Item2; kerningPairs.Add("" + charSet[i] + charSet[j], 1 - Kerning(glyphs[i], glyphs[j], limits[i], limits[j], config)); } } return(kerningPairs); }
/* public void Blur(int radius, int passes) { QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); byte r=0,g=0,b=0,a=0; int summedR, summedG, summedB, summedA; int weight = 0; int xpos, ypos, x, y, kx, ky; for (int pass = 0; pass < passes; pass++) { //horizontal pass for (y = 0; y < bitmap.Height; y++) { for (x = 0; x < bitmap.Width; x++) { summedR = summedG = summedB = summedA = weight = 0; for (kx = -radius; kx <= radius; kx++) { xpos = x + kx; if (xpos >= 0 && xpos < bitmap.Width) { GetPixel32(xpos, y, ref r, ref g, ref b, ref a); summedR += r; summedG += g; summedB += b; summedA += a; weight++; } } summedR /= weight; summedG /= weight; summedB /= weight; summedA /= weight; tmp.PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); } } //vertical pass for (x = 0; x < bitmap.Width; ++x) { for (y = 0; y < bitmap.Height; ++y) { summedR = summedG = summedB = summedA = weight = 0; for (ky = -radius; ky <= radius; ky++) { ypos = y + ky; if (ypos >= 0 && ypos < bitmap.Height) { tmp.GetPixel32(x, ypos, ref r, ref g, ref b, ref a); summedR += r; summedG += g; summedB += b; summedA += a; weight++; } } summedR /= weight; summedG /= weight; summedB /= weight; summedA /= weight; PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); } } } tmp.Free(); }*/ public void BlurAlpha(int radius, int passes) { QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); byte a = 0; int summedA; int weight = 0; int xpos, ypos, x, y, kx, ky; int width = bitmap.Width; int height = bitmap.Height; for (int pass = 0; pass < passes; pass++) { //horizontal pass for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { summedA = weight = 0; for (kx = -radius; kx <= radius; kx++) { xpos = x + kx; if (xpos >= 0 && xpos < width) { GetAlpha32(xpos, y, ref a); summedA += a; weight++; } } summedA /= weight; tmp.PutAlpha32(x, y, (byte)summedA); } } //vertical pass for (x = 0; x <width; ++x) { for (y = 0; y < height; ++y) { summedA = weight = 0; for (ky = -radius; ky <= radius; ky++) { ypos = y + ky; if (ypos >= 0 && ypos < height) { tmp.GetAlpha32(x, ypos,ref a); summedA += a; weight++; } } summedA /= weight; PutAlpha32(x, y, (byte)summedA); } } } tmp.Free(); }
private static void RetargetGlyphRectangleInwards(BitmapData bitmapData, QFontGlyph glyph, bool setYOffset) { int startX, endX; int startY, endY; var rect = glyph.rect; EmptyDel emptyPix; if (bitmapData.PixelFormat == PixelFormat.Format32bppArgb) { emptyPix = delegate(BitmapData data, int x, int y) { return(QBitmap.EmptyAlphaPixel(data, x, y)); } } ; else { emptyPix = delegate(BitmapData data, int x, int y) { return(QBitmap.EmptyPixel(data, x, y)); } }; unsafe { for (startX = rect.X; startX < bitmapData.Width; startX++) { for (int j = rect.Y; j <= rect.Y + rect.Height; j++) { if (!emptyPix(bitmapData, startX, j)) { goto Done1; } } } Done1: for (endX = rect.X + rect.Width; endX >= 0; endX--) { for (int j = rect.Y; j <= rect.Y + rect.Height; j++) { if (!emptyPix(bitmapData, endX, j)) { goto Done2; } } } Done2: for (startY = rect.Y; startY < bitmapData.Height; startY++) { for (int i = startX; i <= endX; i++) { if (!emptyPix(bitmapData, i, startY)) { goto Done3; } } } Done3: for (endY = rect.Y + rect.Height; endY >= 0; endY--) { for (int i = startX; i <= endX; i++) { if (!emptyPix(bitmapData, i, endY)) { goto Done4; } } } Done4 :; } glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1); if (setYOffset) { glyph.yOffset = glyph.rect.Y; } }
/// <summary> /// Generates the final bitmap sheet for the font /// </summary> /// <param name="sourceGlyphs">A collection of <see cref="QFontGlyph"/>s. These are written to the final bitmap</param> /// <param name="sourceBitmaps"> The source bitmaps for the font (initial bitmap)</param> /// <param name="destSheetWidth">The destination bitmap width</param> /// <param name="destSheetHeight">The destination bitmap height</param> /// <param name="destGlyphs">A collection of <see cref="QFontGlyph"/>s that are placed on the final bitmap sheet</param> /// <param name="destMargin">The margin for the final bitmap sheet</param> /// <returns>A collection of <see cref="QBitmap"/>s. These are the final bitmap sheets</returns> private static List <QBitmap> GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin) { var pages = new List <QBitmap>(); destGlyphs = new QFontGlyph[sourceGlyphs.Length]; QBitmap currentPage = null; int maxY = 0; foreach (var glph in sourceGlyphs) { maxY = Math.Max(glph.Rect.Height, maxY); } int finalPageIndex = 0; int finalPageRequiredWidth = 0; int finalPageRequiredHeight = 0; // We loop through the whole process twice. The first time is to determine // the size of the final page, so that we can crop it in advance for (int k = 0; k < 2; k++) { // Whether this is the pre-processing step bool pre = k == 0; int xPos = 0; int yPos = 0; int maxYInRow = 0; int totalTries = 0; // Loop through all the glyphs for (int i = 0; i < sourceGlyphs.Length; i++) { // If this is the second stage and we don't already have a bitmap page, create one if (!pre && currentPage == null) { if (finalPageIndex == pages.Count) { int width = Math.Min(destSheetWidth, finalPageRequiredWidth); int height = Math.Min(destSheetHeight, finalPageRequiredHeight); currentPage = new QBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } else { currentPage = new QBitmap(new Bitmap(destSheetWidth, destSheetHeight, PixelFormat.Format32bppArgb)); currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent } pages.Add(currentPage); } // Keep track of the number of times we've tried to fit the font onto the texture page totalTries++; if (totalTries > 10 * sourceGlyphs.Length) { throw new Exception("Failed to fit font into texture pages"); } var rect = sourceGlyphs[i].Rect; // If we can fit the glyph onto the page, place it if (xPos + rect.Width + 2 * destMargin <= destSheetWidth && yPos + rect.Height + 2 * destMargin <= destSheetHeight) { if (!pre) { // Add to page if (sourceBitmaps[sourceGlyphs[i].Page].PixelFormat == PixelFormat.Format32bppArgb) { QBitmap.Blit(sourceBitmaps[sourceGlyphs[i].Page], currentPage.BitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); } else { QBitmap.BlitMask(sourceBitmaps[sourceGlyphs[i].Page], currentPage.BitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); } // Add to destination glyph collection destGlyphs[i] = new QFontGlyph(pages.Count - 1, new Rectangle(xPos + destMargin, yPos + destMargin, rect.Width, rect.Height), sourceGlyphs[i].YOffset, sourceGlyphs[i].Character); } else { // Update the final dimensions finalPageRequiredWidth = Math.Max(finalPageRequiredWidth, xPos + rect.Width + 2 * destMargin); finalPageRequiredHeight = Math.Max(finalPageRequiredHeight, yPos + rect.Height + 2 * destMargin); } // Update the current x position xPos += rect.Width + 2 * destMargin; // Update the maximum row height so far maxYInRow = Math.Max(maxYInRow, rect.Height); continue; } // If we reach this, haven't been able to fit glyph onto row // Move down one row and try again if (xPos + rect.Width + 2 * destMargin > destSheetWidth) { // Retry the current glyph on the next row i--; // Change coordinates to next row yPos += maxYInRow + 2 * destMargin; xPos = 0; // Is the next row off the bitmap sheet? if (yPos + maxY + 2 * destMargin > destSheetHeight) { // Reset y position yPos = 0; if (!pre) { // If this is not the second stage, reset the currentPage // This will create a new one on next loop currentPage = null; } else { // If this is the pre-processing stage, update // the finalPageIndex. Reset width and height // since we clearly need one full page and extra finalPageRequiredWidth = 0; finalPageRequiredHeight = 0; finalPageIndex++; } } } } } return(pages); }