private Texture FindMatch(Pixmap baseRes) { if (baseRes == null) { return null; } else if (baseRes.IsDefaultContent) { var defaultContent = ContentProvider.GetDefaultContent<Resource>(); return defaultContent.Res().OfType<Texture>().FirstOrDefault(r => r.BasePixmap == baseRes); } else { // First try a direct approach string fileExt = Resource.GetFileExtByType<Texture>(); string targetPath = baseRes.FullName + fileExt; Texture match = ContentProvider.RequestContent<Texture>(targetPath).Res; if (match != null) return match; // If that fails, search for other matches string targetName = baseRes.Name + fileExt; List<string> resFilePaths = Resource.GetResourceFiles(); var resNameMatch = resFilePaths.Where(p => Path.GetFileName(p) == targetName); var resQuery = resNameMatch.Concat(resFilePaths).Distinct(); foreach (string resFile in resQuery) { if (!resFile.EndsWith(fileExt)) continue; match = ContentProvider.RequestContent<Texture>(resFile).Res; if (match != null && match.BasePixmap == baseRes) return match; } // Give up. return null; } }
public void ImportFile(string srcFile, string targetName, string targetDir) { string[] output = this.GetOutputFiles(srcFile, targetName, targetDir); PixelData pixelData = LoadPixelData(srcFile); Pixmap res = new Pixmap(pixelData); res.SourcePath = srcFile; res.Save(output[0]); }
protected override void OnGetValue() { base.OnGetValue(); Pixmap lastValue = this.value; Pixmap[] values = this.GetValue().Cast<Pixmap>().ToArray(); this.value = values.NotNull().FirstOrDefault() as Pixmap; this.ResetPreviewImage(); if (this.value != lastValue) this.Invalidate(); else this.Invalidate(this.rectHeader); }
private void ActionPixmapCreateTexture(Pixmap pixmap) { Texture.CreateFromPixmap(pixmap); }
public void ImportFile(string srcFile, string targetName, string targetDir) { string[] output = this.GetOutputFiles(srcFile, targetName, targetDir); Pixmap res = new Pixmap(srcFile); res.Save(output[0]); }
/// <summary> /// Loads the specified <see cref="Duality.Resources.Pixmap">Pixmaps</see> pixel data. /// </summary> /// <param name="basePixmap">The <see cref="Duality.Resources.Pixmap"/> that is used as pixel data source.</param> /// <param name="sizeMode">Specifies behaviour in case the source data has non-power-of-two dimensions.</param> public void LoadData(ContentRef <Pixmap> basePixmap, SizeMode sizeMode) { DualityApp.GuardSingleThreadState(); if (this.glTexId == 0) { this.glTexId = GL.GenTexture(); } this.needsReload = false; this.basePixmap = basePixmap; this.texSizeMode = sizeMode; int lastTexId; GL.GetInteger(GetPName.TextureBinding2D, out lastTexId); GL.BindTexture(TextureTarget.Texture2D, this.glTexId); if (!this.basePixmap.IsExplicitNull) { Pixmap.Layer pixelData = null; Pixmap basePixmapRes = this.basePixmap.IsAvailable ? this.basePixmap.Res : null; if (basePixmapRes != null) { pixelData = basePixmapRes.MainLayer; this.atlas = basePixmapRes.Atlas != null?basePixmapRes.Atlas.ToArray() : null; this.animCols = basePixmapRes.AnimCols; this.animRows = basePixmapRes.AnimRows; } if (pixelData == null) { pixelData = Pixmap.Checkerboard.Res.MainLayer; } this.AdjustSize(pixelData.Width, pixelData.Height); this.SetupOpenGLRes(); if (this.texSizeMode != SizeMode.NonPowerOfTwo && (this.pxWidth != this.texWidth || this.pxHeight != this.texHeight)) { if (this.texSizeMode == SizeMode.Enlarge) { Pixmap.Layer oldData = pixelData; pixelData = oldData.CloneResize(this.texWidth, this.texHeight); // Fill border pixels manually - that's cheaper than ColorTransparentPixels here. oldData.DrawOnto(pixelData, BlendMode.Solid, this.pxWidth, 0, 1, this.pxHeight, this.pxWidth - 1, 0); oldData.DrawOnto(pixelData, BlendMode.Solid, 0, this.pxHeight, this.pxWidth, 1, 0, this.pxHeight - 1); } else { pixelData = pixelData.CloneRescale(this.texWidth, this.texHeight, Pixmap.FilterMethod.Linear); } } // Load pixel data to video memory GL.TexImage2D(TextureTarget.Texture2D, 0, this.pixelformat, pixelData.Width, pixelData.Height, 0, GLPixelFormat.Rgba, PixelType.UnsignedByte, pixelData.Data); // Adjust atlas to represent UV coordinates if (this.atlas != null) { Vector2 scale; scale.X = this.uvRatio.X / this.pxWidth; scale.Y = this.uvRatio.Y / this.pxHeight; for (int i = 0; i < this.atlas.Length; i++) { this.atlas[i].X *= scale.X; this.atlas[i].W *= scale.X; this.atlas[i].Y *= scale.Y; this.atlas[i].H *= scale.Y; } } } else { this.atlas = null; this.animCols = 0; this.animRows = 0; this.AdjustSize(this.size.X, this.size.Y); this.SetupOpenGLRes(); } GL.BindTexture(TextureTarget.Texture2D, lastTexId); }
/// <summary> /// Renders a text to the specified target <see cref="Duality.Resources.Pixmap"/> <see cref="Duality.Resources.Pixmap.Layer"/>. /// </summary> /// <param name="text"></param> /// <param name="target"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="clr"></param> public void RenderToBitmap(string text, Pixmap.Layer target, float x, float y, ColorRgba clr) { Pixmap.Layer pixelData = this.pixelData.MainLayer; float curOffset = 0.0f; GlyphData glyphData; Rect uvRect; float glyphXOff; float glyphXAdv; for (int i = 0; i < text.Length; i++) { this.ProcessTextAdv(text, i, out glyphData, out uvRect, out glyphXAdv, out glyphXOff); Vector2 dataCoord = uvRect.Pos * new Vector2(this.pixelData.Width, this.pixelData.Height) / this.texture.UVRatio; pixelData.DrawOnto(target, BlendMode.Alpha, MathF.RoundToInt(x + curOffset + glyphXOff), MathF.RoundToInt(y), glyphData.width, glyphData.height, MathF.RoundToInt(dataCoord.X), MathF.RoundToInt(dataCoord.Y), clr); curOffset += glyphXAdv; } }
/// <summary> /// Applies a new set of rendered glyphs to the <see cref="Font"/>, adjusts its typeface metadata and clears out the <see cref="GlyphsDirty"/> flag. /// This method is used by the editor to update a Font after adjusting its properties. /// </summary> /// <param name="bitmap"></param> /// <param name="atlas"></param> /// <param name="glyphs"></param> /// <param name="metrics"></param> public void SetGlyphData(PixelData bitmap, Rect[] atlas, GlyphData[] glyphs, FontMetrics metrics) { this.ReleaseResources(); this.glyphs = glyphs; this.GenerateCharLookup(); this.pixelData = new Pixmap(bitmap); this.pixelData.Atlas = atlas.ToList(); this.metrics = metrics; // Copy metrics data into local fields. // Remove this on the next major version step. this.size = metrics.Size; this.height = metrics.Height; this.ascent = metrics.Ascent; this.bodyAscent = metrics.BodyAscent; this.descent = metrics.Descent; this.baseLine = metrics.BaseLine; this.monospace = metrics.Monospace; this.maxGlyphWidth = 0; for (int i = 0; i < this.glyphs.Length; i++) { this.maxGlyphWidth = Math.Max(this.maxGlyphWidth, this.glyphs[i].Width); } this.UpdateKerningData(); this.GenerateTexture(); this.GenerateMaterial(); }
private Pixmap.Layer CreateDiffImage(Pixmap.Layer first, Pixmap.Layer second) { if (first == second) return new Pixmap.Layer(first.Width, first.Height); if (first.Width != second.Width) return new Pixmap.Layer(1, 1); if (first.Height != second.Height) return new Pixmap.Layer(1, 1); Pixmap.Layer diff = new Pixmap.Layer(first.Width, first.Height); ColorRgba[] firstData = first.Data; ColorRgba[] secondData = second.Data; ColorRgba[] diffData = diff.Data; for (int i = 0; i < firstData.Length; i++) { diffData[i].R = (byte)MathF.Abs(firstData[i].R - secondData[i].R); diffData[i].G = (byte)MathF.Abs(firstData[i].G - secondData[i].G); diffData[i].B = (byte)MathF.Abs(firstData[i].B - secondData[i].B); diffData[i].A = (byte)MathF.Abs(firstData[i].A - secondData[i].A); } return diff; }
private static bool IsMatch(Pixmap source, Texture target) { return target.BasePixmap == source; }
private static void DrawTileHighlights(Canvas canvas, ICmpTilemapRenderer renderer, Point2 origin, IReadOnlyGrid<bool> highlight, ColorRgba fillTint, ColorRgba outlineTint, TileHighlightMode mode, List<Vector2[]> outlineCache = null) { if (highlight.Width == 0 || highlight.Height == 0) return; // Generate strippled line texture if not available yet if (strippledLineTex == null) { PixelData pixels = new PixelData(8, 1); for (int i = 0; i < pixels.Width / 2; i++) pixels[i, 0] = ColorRgba.White; for (int i = pixels.Width / 2; i < pixels.Width; i++) pixels[i, 0] = ColorRgba.TransparentWhite; using (Pixmap pixmap = new Pixmap(pixels)) { strippledLineTex = new Texture(pixmap, TextureSizeMode.Default, TextureMagFilter.Nearest, TextureMinFilter.Nearest, TextureWrapMode.Repeat, TextureWrapMode.Repeat, TexturePixelFormat.Rgba); } } BatchInfo defaultMaterial = new BatchInfo(DrawTechnique.Alpha, canvas.State.Material.MainColor); BatchInfo strippleMaterial = new BatchInfo(DrawTechnique.Alpha, canvas.State.Material.MainColor, strippledLineTex); bool uncertain = (mode & TileHighlightMode.Uncertain) != 0; bool selection = (mode & TileHighlightMode.Selection) != 0; Component component = renderer as Component; Transform transform = component.GameObj.Transform; Tilemap tilemap = renderer.ActiveTilemap; Tileset tileset = tilemap != null ? tilemap.Tileset.Res : null; Vector2 tileSize = tileset != null ? tileset.TileSize : Tileset.DefaultTileSize; Rect localRect = renderer.LocalTilemapRect; // Determine the object's local coordinate system (rotated, scaled) in world space Vector2 worldAxisX = Vector2.UnitX; Vector2 worldAxisY = Vector2.UnitY; MathF.TransformCoord(ref worldAxisX.X, ref worldAxisX.Y, transform.Angle, transform.Scale); MathF.TransformCoord(ref worldAxisY.X, ref worldAxisY.Y, transform.Angle, transform.Scale); Vector2 localOriginPos = tileSize * origin; Vector2 worldOriginPos = localOriginPos.X * worldAxisX + localOriginPos.Y * worldAxisY; canvas.PushState(); { // Configure the canvas so our shapes are properly rotated and scaled canvas.State.TransformHandle = -localRect.TopLeft; canvas.State.TransformAngle = transform.Angle; canvas.State.TransformScale = new Vector2(transform.Scale); // Fill all highlighted tiles that are currently visible { canvas.State.SetMaterial(defaultMaterial); canvas.State.ColorTint = fillTint * ColorRgba.White.WithAlpha(selection ? 0.2f : 0.375f); // Determine tile visibility Vector2 worldTilemapOriginPos = localRect.TopLeft; MathF.TransformCoord(ref worldTilemapOriginPos.X, ref worldTilemapOriginPos.Y, transform.Angle, transform.Scale); TilemapCulling.TileInput cullingIn = new TilemapCulling.TileInput { // Remember: All these transform values are in world space TilemapPos = transform.Pos + new Vector3(worldTilemapOriginPos) + new Vector3(worldOriginPos), TilemapScale = transform.Scale, TilemapAngle = transform.Angle, TileCount = new Point2(highlight.Width, highlight.Height), TileSize = tileSize }; TilemapCulling.TileOutput cullingOut = TilemapCulling.GetVisibleTileRect(canvas.DrawDevice, cullingIn); int renderedTileCount = cullingOut.VisibleTileCount.X * cullingOut.VisibleTileCount.Y; // Draw all visible highlighted tiles { Point2 tileGridPos = cullingOut.VisibleTileStart; Vector2 renderStartPos = worldOriginPos + tileGridPos.X * tileSize.X * worldAxisX + tileGridPos.Y * tileSize.Y * worldAxisY;; Vector2 renderPos = renderStartPos; Vector2 tileXStep = worldAxisX * tileSize.X; Vector2 tileYStep = worldAxisY * tileSize.Y; int lineMergeCount = 0; int totalRects = 0; for (int tileIndex = 0; tileIndex < renderedTileCount; tileIndex++) { bool current = highlight[tileGridPos.X, tileGridPos.Y]; if (current) { // Try to merge consecutive rects in the same line to reduce drawcalls / CPU load bool hasNext = (tileGridPos.X + 1 < highlight.Width) && ((tileGridPos.X + 1 - cullingOut.VisibleTileStart.X) < cullingOut.VisibleTileCount.X); bool next = hasNext ? highlight[tileGridPos.X + 1, tileGridPos.Y] : false; if (next) { lineMergeCount++; } else { totalRects++; canvas.FillRect( transform.Pos.X + renderPos.X - lineMergeCount * tileXStep.X, transform.Pos.Y + renderPos.Y - lineMergeCount * tileXStep.Y, transform.Pos.Z, tileSize.X * (1 + lineMergeCount), tileSize.Y); lineMergeCount = 0; } } tileGridPos.X++; renderPos += tileXStep; if ((tileGridPos.X - cullingOut.VisibleTileStart.X) >= cullingOut.VisibleTileCount.X) { tileGridPos.X = cullingOut.VisibleTileStart.X; tileGridPos.Y++; renderPos = renderStartPos; renderPos += tileYStep * (tileGridPos.Y - cullingOut.VisibleTileStart.Y); } } } } // Draw highlight area outlines, unless flagged as uncertain if (!uncertain) { // Determine the outlines of individual highlighted tile patches if (outlineCache == null) outlineCache = new List<Vector2[]>(); if (outlineCache.Count == 0) { GetTileAreaOutlines(highlight, tileSize, ref outlineCache); } // Draw outlines around all highlighted tile patches canvas.State.SetMaterial(selection ? strippleMaterial : defaultMaterial); canvas.State.ColorTint = outlineTint; foreach (Vector2[] outline in outlineCache) { // For strippled-line display, determine total length of outline if (selection) { float totalLength = 0.0f; for (int i = 1; i < outline.Length; i++) { totalLength += (outline[i - 1] - outline[i]).Length; } canvas.State.TextureCoordinateRect = new Rect(totalLength / strippledLineTex.PixelWidth, 1.0f); } // Draw the outline canvas.DrawPolygon( outline, transform.Pos.X + worldOriginPos.X, transform.Pos.Y + worldOriginPos.Y, transform.Pos.Z); } } // If this is an uncertain highlight, i.e. not actually reflecting the represented action, // draw a gizmo to indicate this for the user. if (uncertain) { Vector2 highlightSize = new Vector2(highlight.Width * tileSize.X, highlight.Height * tileSize.Y); Vector2 highlightCenter = highlightSize * 0.5f; Vector3 circlePos = transform.Pos + new Vector3(worldOriginPos + worldAxisX * highlightCenter + worldAxisY * highlightCenter); float circleRadius = MathF.Min(tileSize.X, tileSize.Y) * 0.2f; canvas.State.SetMaterial(defaultMaterial); canvas.State.ColorTint = outlineTint; canvas.FillCircle( circlePos.X, circlePos.Y, circlePos.Z, circleRadius); } } canvas.PopState(); }
public FontRasterizer(FontFamily fontFamily, float emSize, FontStyle style, string extendedSet, bool antialiasing, bool monospace, FontRenderMode renderMode) { fontData = this.RenderGlyphs( fontFamily, emSize, style, !string.IsNullOrEmpty(extendedSet) ? new FontCharSet(extendedSet) : null, renderMode, antialiasing, monospace); FontGlyphData[] glyphs = fontData.Glyphs; if (glyphs == null) { this.charLookup = new int[0]; return; } int maxCharVal = 0; for (int i = 0; i < glyphs.Length; i++) { maxCharVal = Math.Max(maxCharVal, (int)glyphs[i].Glyph); } this.charLookup = new int[maxCharVal + 1]; for (int i = 0; i < glyphs.Length; i++) { this.charLookup[(int)glyphs[i].Glyph] = i; } this.pixmap = new Pixmap(fontData.Bitmap); this.pixmap.Atlas = fontData.Atlas.ToList(); bool isPixelGridAligned = renderMode == FontRenderMode.MonochromeBitmap || renderMode == FontRenderMode.GrayscaleBitmap || renderMode == FontRenderMode.ClearType; this.texture = new Texture(this.pixmap, TextureSizeMode.Enlarge, isPixelGridAligned ? TextureMagFilter.Nearest : TextureMagFilter.Linear, isPixelGridAligned ? TextureMinFilter.Nearest : TextureMinFilter.LinearMipmapLinear); // Select DrawTechnique to use ContentRef <DrawTechnique> technique; if (renderMode == FontRenderMode.ClearType) { technique = DrawTechnique.Alpha; } else if (renderMode == FontRenderMode.MonochromeBitmap) { technique = DrawTechnique.Mask; } else if (renderMode == FontRenderMode.GrayscaleBitmap) { technique = DrawTechnique.Alpha; } else if (renderMode == FontRenderMode.SmoothBitmap) { technique = DrawTechnique.Alpha; } else { technique = DrawTechnique.SharpAlpha; } // Create and configure internal BatchInfo BatchInfo matInfo = new BatchInfo(technique, this.texture); if (technique == DrawTechnique.SharpAlpha) { matInfo.SetValue("smoothness", this.fontData.Metrics.Size * 4.0f); } this.material = new Material(matInfo); this.kerningLookup = new FontKerningLookup(this.fontData.KerningPairs); }
/// <summary> /// Renders a text to the specified target Image. /// </summary> /// <param name="text"></param> /// <param name="target"></param> public void RenderToBitmap(string text, Pixmap.Layer target, float x = 0.0f, float y = 0.0f, Pixmap.Layer icons = null) { // Rendering int fontNum = this.fonts != null ? this.fonts.Length : 0; RenderState state = new RenderState(this); Element elem; while ((elem = state.NextElement()) != null) { if (elem is TextElement && state.Font != null) { TextElement textElem = elem as TextElement; state.Font.RenderToBitmap( state.CurrentElemText, target, x + state.CurrentElemOffset.X, y + state.CurrentElemOffset.Y + state.LineBaseLine - state.Font.BaseLine, state.Color); } else if (elem is IconElement) { IconElement iconElem = elem as IconElement; Icon icon = iconElem.IconIndex >= 0 && iconElem.IconIndex < this.icons.Length ? this.icons[iconElem.IconIndex] : new Icon(); Vector2 iconSize = icon.size; Rect iconUvRect = icon.uvRect; Vector2 dataCoord = iconUvRect.Pos * new Vector2(icons.Width, icons.Height); Vector2 dataSize = iconUvRect.Size * new Vector2(icons.Width, icons.Height); Pixmap.Layer iconLayer = icons.CloneSubImage( MathF.RoundToInt(dataCoord.X), MathF.RoundToInt(dataCoord.Y), MathF.RoundToInt(dataSize.X), MathF.RoundToInt(dataSize.Y)); iconLayer.Rescale( MathF.RoundToInt(iconSize.X), MathF.RoundToInt(iconSize.Y)); iconLayer.DrawOnto(target, BlendMode.Alpha, MathF.RoundToInt(x + state.CurrentElemOffset.X), MathF.RoundToInt(y + state.CurrentElemOffset.Y + state.LineBaseLine - iconSize.Y), iconLayer.Width, iconLayer.Height, 0, 0, state.Color); } } }
private void ReleaseResources() { if (this.material != null) this.material.Dispose(); if (this.texture != null) this.texture.Dispose(); if (this.pixelData != null) this.pixelData.Dispose(); this.material = null; this.texture = null; this.pixelData = null; this.glyphsDirty = true; }
/// <summary> /// Applies a new set of rendered glyphs to the <see cref="Font"/>, adjusts its typeface metadata and clears out the <see cref="GlyphsDirty"/> flag. /// This method is used by the editor to update a Font after adjusting its properties. /// </summary> /// <param name="bitmap"></param> /// <param name="atlas"></param> /// <param name="glyphs"></param> /// <param name="height"></param> /// <param name="ascent"></param> /// <param name="bodyAscent"></param> /// <param name="descent"></param> /// <param name="baseLine"></param> public void SetGlyphData(PixelData bitmap, Rect[] atlas, GlyphData[] glyphs, int height, int ascent, int bodyAscent, int descent, int baseLine) { this.ReleaseResources(); this.glyphs = glyphs; this.GenerateCharLookup(); this.pixelData = new Pixmap(bitmap); this.pixelData.Atlas = atlas.ToList(); this.height = height; this.ascent = ascent; this.bodyAscent = bodyAscent; this.descent = descent; this.baseLine = baseLine; for (int i = 0; i < this.glyphs.Length; i++) { this.maxGlyphWidth = Math.Max(this.maxGlyphWidth, this.glyphs[i].Width); } this.UpdateKerningData(); this.GenerateTexMat(); this.glyphsDirty = false; }
private void GenerateResources() { if (this.mat != null || this.texture != null || this.pixelData != null) this.ReleaseResources(); int cols; int rows; cols = rows = (int)Math.Ceiling(Math.Sqrt(SupportedChars.Length)); Pixmap.Layer pixelLayer = new Pixmap.Layer(MathF.RoundToInt(cols * this.internalFont.Size), MathF.RoundToInt(rows * (this.internalFont.Height + 1))); Pixmap.Layer glyphTemp; Pixmap.Layer glyphTempTypo; Bitmap bm; Bitmap measureBm = new Bitmap(1, 1); Rect[] atlas = new Rect[SupportedChars.Length]; using (Graphics measureGraphics = Graphics.FromImage(measureBm)) { Brush fntBrush = new SolidBrush(Color.Black); StringFormat formatDef = StringFormat.GenericDefault; formatDef.LineAlignment = StringAlignment.Near; formatDef.FormatFlags = 0; StringFormat formatTypo = StringFormat.GenericTypographic; formatTypo.LineAlignment = StringAlignment.Near; int x = 0; int y = 0; for (int i = 0; i < SupportedChars.Length; ++i) { string str = SupportedChars[i].ToString(CultureInfo.InvariantCulture); bool isSpace = str == " "; SizeF charSize = measureGraphics.MeasureString(str, this.internalFont, pixelLayer.Width, formatDef); // Render a single glyph bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), this.internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = (System.Drawing.Text.TextRenderingHint)this.hint; glyphGraphics.DrawString(str, this.internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatDef); } glyphTemp = new Pixmap.Layer(bm); if (!isSpace) { Rectangle glyphTempBounds = glyphTemp.OpaqueBounds(); glyphTemp.SubImage(glyphTempBounds.X, 0, glyphTempBounds.Width, glyphTemp.Height); if (BodyAscentRef.Contains(SupportedChars[i])) this.bodyAscent += glyphTempBounds.Height; // Render a single glyph in typographic mode bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), this.internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = (System.Drawing.Text.TextRenderingHint)this.hint; glyphGraphics.DrawString(str, this.internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatTypo); } glyphTempTypo = new Pixmap.Layer(bm); glyphTempTypo.Crop(true, false); } else glyphTempTypo = glyphTemp; // Update xy values if it doesn't fit anymore if (x + glyphTemp.Width + 2 > pixelLayer.Width) { x = 0; y += (this.internalFont.Height + 1) + 2; } // Memorize atlas coordinates & glyph data this.maxGlyphWidth = Math.Max(this.maxGlyphWidth, glyphTemp.Width); this.glyphs[i].width = glyphTemp.Width; this.glyphs[i].height = glyphTemp.Height; this.glyphs[i].offsetX = glyphTemp.Width - glyphTempTypo.Width; if (isSpace) { this.glyphs[i].width /= 2; this.glyphs[i].offsetX /= 2; } atlas[i].X = x; atlas[i].Y = y; atlas[i].W = glyphTemp.Width; atlas[i].H = (this.internalFont.Height + 1); // Draw it onto the font surface glyphTemp.DrawOnto(pixelLayer, BlendMode.Solid, x, y); x += glyphTemp.Width + 2; } } // Adjust colors based on alpha value //for (int i = 0; i < pixelLayer.Data.Length; i++) System.Threading.Tasks.Parallel.For(0, pixelLayer.Data.Length, i => { float factor = pixelLayer.Data[i].A / 255.0f; float invFactor = 1.0f - factor; pixelLayer.Data[i].R = (byte)Math.Min(255.0f, Math.Max(0.0f, this.bgColor.R * invFactor + this.color.R * factor)); pixelLayer.Data[i].G = (byte)Math.Min(255.0f, Math.Max(0.0f, this.bgColor.G * invFactor + this.color.G * factor)); pixelLayer.Data[i].B = (byte)Math.Min(255.0f, Math.Max(0.0f, this.bgColor.B * invFactor + this.color.B * factor)); pixelLayer.Data[i].A = (byte)Math.Min(255.0f, Math.Max(0.0f, this.bgColor.A * invFactor + this.color.A * factor)); }); this.height = this.internalFont.Height; this.ascent = (int)Math.Round(this.internalFont.FontFamily.GetCellAscent(this.internalFont.Style) * this.internalFont.Size / this.internalFont.FontFamily.GetEmHeight(this.internalFont.Style)); this.bodyAscent /= BodyAscentRef.Length; this.descent = (int)Math.Round(this.internalFont.FontFamily.GetCellDescent(this.internalFont.Style) * this.internalFont.GetHeight() / this.internalFont.FontFamily.GetLineSpacing(this.internalFont.Style)); this.baseLine = (int)Math.Round(this.internalFont.FontFamily.GetCellAscent(this.internalFont.Style) * this.internalFont.GetHeight() / this.internalFont.FontFamily.GetLineSpacing(this.internalFont.Style)); bool useNearest = this.hint == RenderHint.Monochrome || !this.filtering; this.pixelData = new Pixmap(pixelLayer); this.pixelData.Atlas = new List<Rect>(atlas); this.texture = new Texture(this.pixelData, Texture.SizeMode.Enlarge, useNearest ? OpenTK.Graphics.OpenGL.TextureMagFilter.Nearest : OpenTK.Graphics.OpenGL.TextureMagFilter.Linear, useNearest ? OpenTK.Graphics.OpenGL.TextureMinFilter.Nearest : OpenTK.Graphics.OpenGL.TextureMinFilter.LinearMipmapLinear); this.mat = new Material(this.hint == RenderHint.Monochrome ? DrawTechnique.Mask : DrawTechnique.Alpha, ColorRgba.White, this.texture); }
private void ActionPixmapOpenRes(Pixmap pixmap) { if (pixmap == null) return; FileImportProvider.OpenSourceFile(pixmap, ".png", pixmap.SavePixelData); }
private static bool IsMatch(Pixmap source, Tileset target) { return target.RenderConfig != null && target.RenderConfig.Any(config => config.SourceData == source); }
/// <summary> /// Loads the specified <see cref="Duality.Resources.Pixmap">Pixmaps</see> pixel data. /// </summary> /// <param name="basePixmap">The <see cref="Duality.Resources.Pixmap"/> that is used as pixel data source.</param> /// <param name="sizeMode">Specifies behaviour in case the source data has non-power-of-two dimensions.</param> public void LoadData(ContentRef <Pixmap> basePixmap, TextureSizeMode sizeMode) { if (this.nativeTex == null) { this.nativeTex = DualityApp.GraphicsBackend.CreateTexture(); } this.needsReload = false; this.basePixmap = basePixmap; this.texSizeMode = sizeMode; if (!this.basePixmap.IsExplicitNull) { PixelData pixelData = null; Pixmap basePixmapRes = this.basePixmap.IsAvailable ? this.basePixmap.Res : null; if (basePixmapRes != null) { pixelData = basePixmapRes.MainLayer; this.atlas = basePixmapRes.Atlas != null?basePixmapRes.Atlas.ToArray() : null; } if (pixelData == null) { pixelData = Pixmap.Checkerboard.Res.MainLayer; } this.AdjustSize(pixelData.Width, pixelData.Height); this.SetupNativeRes(); if (this.texSizeMode != TextureSizeMode.NonPowerOfTwo && (this.pxWidth != this.texWidth || this.pxHeight != this.texHeight)) { if (this.texSizeMode == TextureSizeMode.Enlarge) { PixelData oldData = pixelData; pixelData = oldData.CloneResize(this.texWidth, this.texHeight); // Fill border pixels manually - that's cheaper than ColorTransparentPixels here. oldData.DrawOnto(pixelData, BlendMode.Solid, this.pxWidth, 0, 1, this.pxHeight, this.pxWidth - 1, 0); oldData.DrawOnto(pixelData, BlendMode.Solid, 0, this.pxHeight, this.pxWidth, 1, 0, this.pxHeight - 1); } else { pixelData = pixelData.CloneRescale(this.texWidth, this.texHeight, ImageScaleFilter.Linear); } } // Load pixel data to video memory this.nativeTex.LoadData( this.pixelformat, pixelData.Width, pixelData.Height, pixelData.Data, ColorDataLayout.Rgba, ColorDataElementType.Byte); // Adjust atlas to represent UV coordinates if (this.atlas != null) { Vector2 scale; scale.X = this.uvRatio.X / this.pxWidth; scale.Y = this.uvRatio.Y / this.pxHeight; for (int i = 0; i < this.atlas.Length; i++) { this.atlas[i].X *= scale.X; this.atlas[i].W *= scale.X; this.atlas[i].Y *= scale.Y; this.atlas[i].H *= scale.Y; } } } else { this.atlas = null; this.AdjustSize(this.size.X, this.size.Y); this.SetupNativeRes(); } }
private void GenerateResources() { if (this.mat != null || this.texture != null || this.pixelData != null) this.ReleaseResources(); TextRenderingHint textRenderingHint; if (this.renderMode == RenderMode.MonochromeBitmap) textRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit; else textRenderingHint = TextRenderingHint.AntiAliasGridFit; int cols; int rows; cols = rows = (int)Math.Ceiling(Math.Sqrt(SupportedChars.Length)); Pixmap.Layer pixelLayer = new Pixmap.Layer(MathF.RoundToInt(cols * this.internalFont.Size * 1.2f), MathF.RoundToInt(rows * this.internalFont.Height * 1.2f)); Pixmap.Layer glyphTemp; Pixmap.Layer glyphTempTypo; Bitmap bm; Bitmap measureBm = new Bitmap(1, 1); Rect[] atlas = new Rect[SupportedChars.Length]; using (Graphics measureGraphics = Graphics.FromImage(measureBm)) { Brush fntBrush = new SolidBrush(Color.Black); StringFormat formatDef = StringFormat.GenericDefault; formatDef.LineAlignment = StringAlignment.Near; formatDef.FormatFlags = 0; StringFormat formatTypo = StringFormat.GenericTypographic; formatTypo.LineAlignment = StringAlignment.Near; int x = 1; int y = 1; for (int i = 0; i < SupportedChars.Length; ++i) { string str = SupportedChars[i].ToString(CultureInfo.InvariantCulture); bool isSpace = str == " "; SizeF charSize = measureGraphics.MeasureString(str, this.internalFont, pixelLayer.Width, formatDef); // Rasterize a single glyph for rendering bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), this.internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, this.internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatDef); } glyphTemp = new Pixmap.Layer(bm); // Rasterize a single glyph in typographic mode for metric analysis if (!isSpace) { Rectangle glyphTempBounds = glyphTemp.OpaqueBounds(); glyphTemp.SubImage(glyphTempBounds.X, 0, glyphTempBounds.Width, glyphTemp.Height); if (BodyAscentRef.Contains(SupportedChars[i])) this.bodyAscent += glyphTempBounds.Height; bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), this.internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, this.internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatTypo); } glyphTempTypo = new Pixmap.Layer(bm); glyphTempTypo.Crop(true, false); } else { glyphTempTypo = glyphTemp; } // Update xy values if it doesn't fit anymore if (x + glyphTemp.Width + 2 > pixelLayer.Width) { x = 1; y += this.internalFont.Height + MathF.Clamp((int)MathF.Ceiling(this.internalFont.Height * 0.1875f), 3, 10); } // Memorize atlas coordinates & glyph data this.maxGlyphWidth = Math.Max(this.maxGlyphWidth, glyphTemp.Width); this.glyphs[i].width = glyphTemp.Width; this.glyphs[i].height = glyphTemp.Height; this.glyphs[i].offsetX = glyphTemp.Width - glyphTempTypo.Width; if (isSpace) { this.glyphs[i].width /= 2; this.glyphs[i].offsetX /= 2; } atlas[i].X = x; atlas[i].Y = y; atlas[i].W = glyphTemp.Width; atlas[i].H = (this.internalFont.Height + 1); // Draw it onto the font surface glyphTemp.DrawOnto(pixelLayer, BlendMode.Solid, x, y); x += glyphTemp.Width + MathF.Clamp((int)MathF.Ceiling(this.internalFont.Height * 0.125f), 2, 10); } } // White out texture except alpha channel. for (int i = 0; i < pixelLayer.Data.Length; i++) { pixelLayer.Data[i].R = 255; pixelLayer.Data[i].G = 255; pixelLayer.Data[i].B = 255; } // Determine Font properties this.height = this.internalFont.Height; this.ascent = (int)Math.Round(this.internalFont.FontFamily.GetCellAscent(this.internalFont.Style) * this.internalFont.Size / this.internalFont.FontFamily.GetEmHeight(this.internalFont.Style)); this.bodyAscent /= BodyAscentRef.Length; this.descent = (int)Math.Round(this.internalFont.FontFamily.GetCellDescent(this.internalFont.Style) * this.internalFont.GetHeight() / this.internalFont.FontFamily.GetLineSpacing(this.internalFont.Style)); this.baseLine = (int)Math.Round(this.internalFont.FontFamily.GetCellAscent(this.internalFont.Style) * this.internalFont.GetHeight() / this.internalFont.FontFamily.GetLineSpacing(this.internalFont.Style)); // Create internal Pixmap and Texture Resources this.pixelData = new Pixmap(pixelLayer); this.pixelData.Atlas = new List<Rect>(atlas); this.texture = new Texture(this.pixelData, Texture.SizeMode.Enlarge, this.IsPixelGridAligned ? TextureMagFilter.Nearest : TextureMagFilter.Linear, this.IsPixelGridAligned ? TextureMinFilter.Nearest : TextureMinFilter.LinearMipmapLinear); // Select DrawTechnique to use ContentRef<DrawTechnique> technique; if (this.renderMode == RenderMode.MonochromeBitmap) technique = DrawTechnique.Mask; else if (this.renderMode == RenderMode.GrayscaleBitmap) technique = DrawTechnique.Alpha; else if (this.renderMode == RenderMode.SmoothBitmap) technique = DrawTechnique.Alpha; else technique = DrawTechnique.SharpAlpha; // Create and configure internal BatchInfo BatchInfo matInfo = new BatchInfo(technique, ColorRgba.White, this.texture); if (technique == DrawTechnique.SharpAlpha) { matInfo.SetUniform("smoothness", this.size * 3.0f); } this.mat = new Material(matInfo); }
private void ReleaseResources() { if (this.mat != null) this.mat.Dispose(); if (this.texture != null) this.texture.Dispose(); if (this.pixelData != null) this.pixelData.Dispose(); this.mat = null; this.texture = null; this.pixelData = null; this.needsReload = true; }
private bool AreImagesEqual(Pixmap.Layer first, Pixmap.Layer second) { if (first == second) return true; if (first.Width != second.Width) return false; if (first.Height != second.Height) return false; ColorRgba[] firstData = first.Data; ColorRgba[] secondData = second.Data; float error = 0; float maxError = firstData.Length; // (1/255) off per pixel is probably okay. for (int i = 0; i < firstData.Length; i++) { error += MathF.Abs(firstData[i].R * (firstData[i].A / 255.0f) - secondData[i].R * (secondData[i].A / 255.0f)); error += MathF.Abs(firstData[i].G * (firstData[i].A / 255.0f) - secondData[i].G * (secondData[i].A / 255.0f)); error += MathF.Abs(firstData[i].B * (firstData[i].A / 255.0f) - secondData[i].B * (secondData[i].A / 255.0f)); error += MathF.Abs(firstData[i].A - secondData[i].A); if (error >= maxError) return false; } return true; }
/// <summary> /// Renders a text to the specified target <see cref="Duality.Resources.Pixmap"/> <see cref="Duality.Resources.Pixmap.Layer"/>. /// </summary> /// <param name="text"></param> /// <param name="target"></param> /// <param name="x"></param> /// <param name="y"></param> public void RenderToBitmap(string text, Pixmap.Layer target, float x = 0.0f, float y = 0.0f) { this.RenderToBitmap(text, target, x, y, ColorRgba.White); }
/// <summary> /// Compiles a <see cref="Tileset"/> using its specified source data, in order to /// generate optimized target data for rendering and collision detection. /// </summary> public TilesetCompilerOutput Compile(TilesetCompilerInput input) { TilesetCompilerOutput output = input.ExistingOutput; output.TileData = output.TileData ?? new RawList<TileInfo>(input.TileInput.Count); output.RenderData = output.RenderData ?? new List<Texture>(); output.AutoTileData = output.AutoTileData ?? new List<TilesetAutoTileInfo>(); // Clear existing data, but keep the sufficiently big data structures output.TileData.Clear(); output.RenderData.Clear(); output.AutoTileData.Clear(); // Determine how many source tiles we have int sourceTileCount = int.MaxValue; for (int renderInputIndex = 0; renderInputIndex < input.RenderConfig.Count; renderInputIndex++) { TilesetRenderInput renderInput = input.RenderConfig[renderInputIndex] ?? DefaultRenderInput; PixelData sourceLayerData = (renderInput.SourceData.Res ?? Pixmap.Checkerboard.Res).MainLayer; LayerGeometry layerGeometry = this.CalculateLayerGeometry(renderInput, sourceLayerData); sourceTileCount = Math.Min(sourceTileCount, layerGeometry.SourceTileCount); } if (input.RenderConfig.Count == 0) sourceTileCount = 0; // Transform AutoTile data for (int autoTileIndex = 0; autoTileIndex < input.AutoTileConfig.Count; autoTileIndex++) { TilesetAutoTileInput autoTileInput = input.AutoTileConfig[autoTileIndex]; TilesetAutoTileInfo autoTileInfo = this.TransformAutoTileData( autoTileIndex, autoTileInput, output.TileData, sourceTileCount); output.AutoTileData.Add(autoTileInfo); } // Initialize all tiles to being visually empty. They will be subtractively updated // during output pixel data generation in the next step. { int tileDataCount = output.TileData.Count; TileInfo[] tileData = output.TileData.Data; for (int i = 0; i < tileDataCount; i++) { tileData[i].IsVisuallyEmpty = true; } } // Generate output pixel data for (int renderInputIndex = 0; renderInputIndex < input.RenderConfig.Count; renderInputIndex++) { TilesetRenderInput renderInput = input.RenderConfig[renderInputIndex] ?? DefaultRenderInput; PixelData sourceLayerData = (renderInput.SourceData.Res ?? Pixmap.Checkerboard.Res).MainLayer; // Determine overal geometry values for this layer, such as tile bounds and texture sizes LayerGeometry layerGeometry = this.CalculateLayerGeometry(renderInput, sourceLayerData); // Generate pixel data and atlas values for this layer's texture LayerPixelData targetLayerData = this.GenerateLayerPixelData( renderInput, sourceLayerData, layerGeometry, output.TileData); // Create the texture to be used for this rendering input using (Pixmap targetPixmap = new Pixmap(targetLayerData.PixelData)) { targetPixmap.Atlas = targetLayerData.Atlas; Texture targetTexture = new Texture( targetPixmap, TextureSizeMode.Enlarge, renderInput.TargetMagFilter, renderInput.TargetMinFilter, TextureWrapMode.Clamp, TextureWrapMode.Clamp, renderInput.TargetFormat); output.RenderData.Add(targetTexture); } } // Generate additional per-tile data this.TransformTileData(input.TileInput, output.TileData, output.RenderData); // Apply global tileset stats output.TileCount = sourceTileCount; return output; }
private void LoadOrCreatePixelData(int cols, int rows, TextRenderingHint textRenderingHint) { Pixmap.Layer pixelLayer = new Pixmap.Layer(MathF.RoundToInt(cols*this.internalFont.Size*1.2f), MathF.RoundToInt(rows*this.internalFont.Height*1.2f)); Pixmap.Layer glyphTemp; Pixmap.Layer glyphTempTypo; Bitmap bm; Bitmap measureBm = new Bitmap(1, 1); Rect[] atlas = new Rect[SupportedChars.Length]; using (Graphics measureGraphics = Graphics.FromImage(measureBm)) { Brush fntBrush = new SolidBrush(Color.Black); StringFormat formatDef = StringFormat.GenericDefault; formatDef.LineAlignment = StringAlignment.Near; formatDef.FormatFlags = 0; StringFormat formatTypo = StringFormat.GenericTypographic; formatTypo.LineAlignment = StringAlignment.Near; int x = 1; int y = 1; for (int i = 0; i < SupportedChars.Length; ++i) { string str = SupportedChars[i].ToString(CultureInfo.InvariantCulture); bool isSpace = str == " "; SizeF charSize = measureGraphics.MeasureString(str, this.internalFont, pixelLayer.Width, formatDef); // Rasterize a single glyph for rendering bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), this.internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, this.internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatDef); } glyphTemp = new Pixmap.Layer(bm); // Rasterize a single glyph in typographic mode for metric analysis if (!isSpace) { Rectangle glyphTempBounds = glyphTemp.OpaqueBounds(); glyphTemp.SubImage(glyphTempBounds.X, 0, glyphTempBounds.Width, glyphTemp.Height); if (BodyAscentRef.Contains(SupportedChars[i])) this.bodyAscent += glyphTempBounds.Height; bm = new Bitmap((int) Math.Ceiling(Math.Max(1, charSize.Width)), this.internalFont.Height + 1); using (Graphics glyphGraphics = Graphics.FromImage(bm)) { glyphGraphics.Clear(Color.Transparent); glyphGraphics.TextRenderingHint = textRenderingHint; glyphGraphics.DrawString(str, this.internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatTypo); } glyphTempTypo = new Pixmap.Layer(bm); glyphTempTypo.Crop(true, false); } else { glyphTempTypo = glyphTemp; } // Update xy values if it doesn't fit anymore if (x + glyphTemp.Width + 2 > pixelLayer.Width) { x = 1; y += this.internalFont.Height + MathF.Clamp((int) MathF.Ceiling(this.internalFont.Height*0.1875f), 3, 10); } // Memorize atlas coordinates & glyph data this.maxGlyphWidth = Math.Max(this.maxGlyphWidth, glyphTemp.Width); this.glyphs[i].width = glyphTemp.Width; this.glyphs[i].height = glyphTemp.Height; this.glyphs[i].offsetX = glyphTemp.Width - glyphTempTypo.Width; if (isSpace) { this.glyphs[i].width /= 2; this.glyphs[i].offsetX /= 2; } atlas[i].X = x; atlas[i].Y = y; atlas[i].W = glyphTemp.Width; atlas[i].H = (this.internalFont.Height + 1); // Draw it onto the font surface glyphTemp.DrawOnto(pixelLayer, BlendMode.Solid, x, y); x += glyphTemp.Width + MathF.Clamp((int) MathF.Ceiling(this.internalFont.Height*0.125f), 2, 10); if(glyphTempTypo != glyphTemp) glyphTempTypo.Dispose(); glyphTemp.Dispose(); } } // White out texture except alpha channel. for (int i = 0; i < pixelLayer.Data.Length; i++) { pixelLayer.Data[i].R = 255; pixelLayer.Data[i].G = 255; pixelLayer.Data[i].B = 255; } this.pixelData = new Pixmap(pixelLayer) {Atlas = new List<Rect>(atlas)}; }