Пример #1
0
        /// <summary>
        /// Renders a text to the specified target <see cref="Duality.Drawing.PixelData"/>.
        /// </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, PixelData target, float x, float y, ColorRgba clr)
        {
            if (this.pixelData == null)
            {
                return;
            }

            PixelData bitmap    = this.pixelData.MainLayer;
            float     curOffset = 0.0f;
            GlyphData glyphData;
            Rect      uvRect;
            float     glyphXOff;
            float     glyphYOff;
            float     glyphXAdv;

            for (int i = 0; i < text.Length; i++)
            {
                this.ProcessTextAdv(text, i, out glyphData, out uvRect, out glyphXAdv, out glyphXOff, out glyphYOff);
                Vector2 dataCoord = uvRect.Pos * new Vector2(this.pixelData.Width, this.pixelData.Height) / this.texture.UVRatio;

                bitmap.DrawOnto(target,
                                BlendMode.Alpha,
                                MathF.RoundToInt(x + curOffset + glyphXOff),
                                MathF.RoundToInt(y + glyphYOff),
                                glyphData.Width,
                                glyphData.Height,
                                MathF.RoundToInt(dataCoord.X),
                                MathF.RoundToInt(dataCoord.Y),
                                clr);

                curOffset += glyphXAdv;
            }
        }
Пример #2
0
        /// <summary>
        /// Renders a text to the specified target <see cref="Duality.Drawing.PixelData"/>.
        /// </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, PixelData target, float x, float y, ColorRgba clr)
        {
            if (this.pixmap == null)
            {
                return;
            }

            PixelData     bitmap    = this.pixmap.MainLayer;
            float         curOffset = 0.0f;
            FontGlyphData glyphData;
            Rect          uvRect;
            float         glyphXAdv;

            for (int i = 0; i < text.Length; i++)
            {
                this.ProcessTextAdv(text, i, out glyphData, out uvRect, out glyphXAdv);
                Vector2 dataCoord = uvRect.Pos * bitmap.Size / this.texture.UVRatio;

                bitmap.DrawOnto(target,
                                BlendMode.Alpha,
                                MathF.RoundToInt(x + curOffset - glyphData.Offset.X),
                                MathF.RoundToInt(y - glyphData.Offset.Y),
                                (int)glyphData.Size.X,
                                (int)glyphData.Size.Y,
                                MathF.RoundToInt(dataCoord.X),
                                MathF.RoundToInt(dataCoord.Y),
                                clr);

                curOffset += glyphXAdv;
            }
        }
Пример #3
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, 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;
                    bool hasAtlas = (basePixmapRes.Atlas != null && basePixmapRes.Atlas.Count > 0);
                    this.atlas = hasAtlas ? 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();
            }
        }
Пример #4
0
        /// <summary>
        /// Renders the <see cref="Duality.Resources.Font"/> using the specified system font.
        /// This method assumes that the system font's size and style match the one specified in
        /// the specified Duality font.
        /// </summary>
        private RenderedFontData RenderGlyphs(SysDrawFont internalFont, FontCharSet charSet, bool antialiazing, bool monospace)
        {
            DualityFont.GlyphData[] glyphs = new DualityFont.GlyphData[charSet.Chars.Length];
            for (int i = 0; i < glyphs.Length; i++)
            {
                glyphs[i].Glyph = charSet.Chars[i];
            }

            int bodyAscent = 0;
            int baseLine   = 0;
            int descent    = 0;
            int ascent     = 0;

            TextRenderingHint textRenderingHint;

            if (antialiazing)
            {
                textRenderingHint = TextRenderingHint.AntiAliasGridFit;
            }
            else
            {
                textRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
            }

            int cols;
            int rows;

            cols = rows = (int)Math.Ceiling(Math.Sqrt(glyphs.Length));

            PixelData pixelLayer = new PixelData(
                MathF.RoundToInt(cols * internalFont.Size * 1.2f),
                MathF.RoundToInt(rows * internalFont.Height * 1.2f),
                ColorRgba.TransparentBlack);
            PixelData glyphTemp;
            PixelData glyphTempTypo;
            Bitmap    bm;
            Bitmap    measureBm = new Bitmap(1, 1);

            Rect[] atlas = new Rect[glyphs.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 < glyphs.Length; ++i)
                {
                    string str      = glyphs[i].Glyph.ToString(CultureInfo.InvariantCulture);
                    bool   isSpace  = str == " ";
                    SizeF  charSize = measureGraphics.MeasureString(str, internalFont, pixelLayer.Width, formatDef);

                    // Rasterize a single glyph for rendering
                    bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), internalFont.Height + 1);
                    using (Graphics glyphGraphics = Graphics.FromImage(bm))
                    {
                        glyphGraphics.Clear(Color.Transparent);
                        glyphGraphics.TextRenderingHint = textRenderingHint;
                        glyphGraphics.DrawString(str, internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatDef);
                    }
                    glyphTemp = new PixelData();
                    glyphTemp.FromBitmap(bm);

                    // Rasterize a single glyph in typographic mode for metric analysis
                    if (!isSpace)
                    {
                        Point2 glyphTempOpaqueTopLeft;
                        Point2 glyphTempOpaqueSize;
                        glyphTemp.GetOpaqueBoundaries(out glyphTempOpaqueTopLeft, out glyphTempOpaqueSize);

                        glyphTemp.SubImage(glyphTempOpaqueTopLeft.X, 0, glyphTempOpaqueSize.X, glyphTemp.Height);

                        if (charSet.CharBodyAscentRef.Contains(glyphs[i].Glyph))
                        {
                            bodyAscent += glyphTempOpaqueSize.Y;
                        }
                        if (charSet.CharBaseLineRef.Contains(glyphs[i].Glyph))
                        {
                            baseLine += glyphTempOpaqueTopLeft.Y + glyphTempOpaqueSize.Y;
                        }
                        if (charSet.CharDescentRef.Contains(glyphs[i].Glyph))
                        {
                            descent += glyphTempOpaqueTopLeft.Y + glyphTempOpaqueSize.Y;
                        }

                        bm = new Bitmap((int)Math.Ceiling(Math.Max(1, charSize.Width)), internalFont.Height + 1);
                        using (Graphics glyphGraphics = Graphics.FromImage(bm))
                        {
                            glyphGraphics.Clear(Color.Transparent);
                            glyphGraphics.TextRenderingHint = textRenderingHint;
                            glyphGraphics.DrawString(str, internalFont, fntBrush, new RectangleF(0, 0, bm.Width, bm.Height), formatTypo);
                        }
                        glyphTempTypo = new PixelData();
                        glyphTempTypo.FromBitmap(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 += internalFont.Height + MathF.Clamp((int)MathF.Ceiling(internalFont.Height * 0.1875f), 3, 10);
                    }

                    // Memorize atlas coordinates & glyph data
                    glyphs[i].Width   = glyphTemp.Width;
                    glyphs[i].Height  = glyphTemp.Height;
                    glyphs[i].OffsetX = glyphTemp.Width - glyphTempTypo.Width;
                    glyphs[i].OffsetY = 0;                     // ttf fonts are rendered on blocks that are the whole size of the height - so no need for offset
                    if (isSpace)
                    {
                        glyphs[i].Width   /= 2;
                        glyphs[i].OffsetX /= 2;
                    }
                    atlas[i].X = x;
                    atlas[i].Y = y;
                    atlas[i].W = glyphTemp.Width;
                    atlas[i].H = (internalFont.Height + 1);

                    // Draw it onto the font surface
                    glyphTemp.DrawOnto(pixelLayer, BlendMode.Solid, x, y);

                    x += glyphTemp.Width + MathF.Clamp((int)MathF.Ceiling(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;
            }

            // Monospace offset adjustments
            if (monospace)
            {
                int maxGlyphWidth = 0;
                for (int i = 0; i < glyphs.Length; i++)
                {
                    maxGlyphWidth = Math.Max(maxGlyphWidth, glyphs[i].Width);
                }
                for (int i = 0; i < glyphs.Length; ++i)
                {
                    glyphs[i].OffsetX -= (int)Math.Round((maxGlyphWidth - glyphs[i].Width) / 2.0f);
                }
            }

            // Determine Font properties
            {
                float lineSpacing = internalFont.FontFamily.GetLineSpacing(internalFont.Style);
                float emHeight    = internalFont.FontFamily.GetEmHeight(internalFont.Style);
                float cellAscent  = internalFont.FontFamily.GetCellAscent(internalFont.Style);
                float cellDescent = internalFont.FontFamily.GetCellDescent(internalFont.Style);

                ascent      = (int)Math.Round(cellAscent * internalFont.Size / emHeight);
                bodyAscent /= charSet.CharBodyAscentRef.Length;
                baseLine   /= charSet.CharBaseLineRef.Length;
                descent     = (int)Math.Round(((float)descent / charSet.CharDescentRef.Length) - (float)baseLine);
            }

            // Aggregate rendered and generated data into our return value
            FontMetrics metrics = new FontMetrics(
                size:       internalFont.SizeInPoints,
                height:     (int)internalFont.Height,
                ascent:     ascent,
                bodyAscent: bodyAscent,
                descent:    descent,
                baseLine:   baseLine,
                monospace:  monospace);

            return(new RenderedFontData
            {
                Bitmap = pixelLayer,
                Atlas = atlas,
                GlyphData = glyphs,
                Metrics = metrics
            });
        }
Пример #5
0
        /// <summary>
        /// Composes pixel and atlas data for a single <see cref="Tileset"/> visual layer.
        /// </summary>
        private LayerPixelData ComposeLayerPixelData(TilesetRenderInput renderInput, PixelData sourceData, LayerGeometry geometry)
        {
            // Create a buffer for writing target pixel data
            LayerPixelData target;

            target.PixelData = new PixelData(geometry.TargetTextureSize.X, geometry.TargetTextureSize.Y);
            target.Atlas     = new List <Rect>();

            // Iterate over source tiles and copy each tile from source to target
            Point2 targetTilePos = new Point2(0, 0);

            for (int tileIndex = 0; tileIndex < geometry.SourceTileCount; tileIndex++)
            {
                // Determine source and target positions on pixel data / buffer
                Point2 sourceTilePos    = geometry.GetSourceTilePos(tileIndex);
                Point2 targetContentPos = new Point2(
                    targetTilePos.X + renderInput.TargetTileMargin,
                    targetTilePos.Y + renderInput.TargetTileMargin);

                // Draw the source tile onto the target buffer, including its spacing / border
                sourceData.DrawOnto(target.PixelData,
                                    BlendMode.Solid,
                                    targetContentPos.X,
                                    targetContentPos.Y,
                                    renderInput.SourceTileSize.X,
                                    renderInput.SourceTileSize.Y,
                                    sourceTilePos.X,
                                    sourceTilePos.Y);

                // Fill up the target spacing area with similar pixels
                if (renderInput.TargetTileMargin > 0)
                {
                    FillTileSpacing(target.PixelData, renderInput.TargetTileMargin, targetContentPos, renderInput.SourceTileSize);
                }

                // Update whether the tile is considered visually empty
                if (this.tiles.Data[tileIndex].IsVisuallyEmpty)
                {
                    bool isLayerVisuallyEmpty = IsCompletelyTransparent(
                        sourceData,
                        sourceTilePos,
                        renderInput.SourceTileSize);
                    if (!isLayerVisuallyEmpty)
                    {
                        this.tiles.Data[tileIndex].IsVisuallyEmpty = false;
                    }
                }

                // Add an entry to the generated atlas
                Rect atlasRect = new Rect(
                    targetTilePos.X + renderInput.TargetTileMargin,
                    targetTilePos.Y + renderInput.TargetTileMargin,
                    geometry.TargetTileAdvance.X - renderInput.TargetTileMargin * 2,
                    geometry.TargetTileAdvance.Y - renderInput.TargetTileMargin * 2);
                target.Atlas.Add(atlasRect);

                // Advance the target tile position
                targetTilePos.X += geometry.TargetTileAdvance.X;
                if (targetTilePos.X + geometry.TargetTileAdvance.X > target.PixelData.Width)
                {
                    targetTilePos.X  = 0;
                    targetTilePos.Y += geometry.TargetTileAdvance.Y;
                }
            }

            // Generate additional target tiles as scheduled
            foreach (GeneratedQuadTile tile in this.generateTileSchedule)
            {
                // Determine source and target positions on pixel data / buffer
                Point2 sourceTilePosTopLeft     = geometry.GetSourceTilePos(tile.SourceTopLeftIndex);
                Point2 sourceTilePosTopRight    = geometry.GetSourceTilePos(tile.SourceTopRightIndex);
                Point2 sourceTilePosBottomRight = geometry.GetSourceTilePos(tile.SourceBottomRightIndex);
                Point2 sourceTilePosBottomLeft  = geometry.GetSourceTilePos(tile.SourceBottomLeftIndex);
                Point2 targetContentPos         = new Point2(
                    targetTilePos.X + renderInput.TargetTileMargin,
                    targetTilePos.Y + renderInput.TargetTileMargin);

                // Draw the source tile onto the target buffer, including its spacing / border
                Point2 quadSize = renderInput.SourceTileSize / 2;
                sourceData.DrawOnto(target.PixelData, BlendMode.Solid,
                                    targetContentPos.X,
                                    targetContentPos.Y,
                                    quadSize.X,
                                    quadSize.Y,
                                    sourceTilePosTopLeft.X,
                                    sourceTilePosTopLeft.Y);
                sourceData.DrawOnto(target.PixelData, BlendMode.Solid,
                                    targetContentPos.X + quadSize.X,
                                    targetContentPos.Y,
                                    quadSize.X,
                                    quadSize.Y,
                                    sourceTilePosTopRight.X + quadSize.X,
                                    sourceTilePosTopRight.Y);
                sourceData.DrawOnto(target.PixelData, BlendMode.Solid,
                                    targetContentPos.X + quadSize.X,
                                    targetContentPos.Y + quadSize.Y,
                                    quadSize.X,
                                    quadSize.Y,
                                    sourceTilePosBottomRight.X + quadSize.X,
                                    sourceTilePosBottomRight.Y + quadSize.Y);
                sourceData.DrawOnto(target.PixelData, BlendMode.Solid,
                                    targetContentPos.X,
                                    targetContentPos.Y + quadSize.Y,
                                    quadSize.X,
                                    quadSize.Y,
                                    sourceTilePosBottomLeft.X,
                                    sourceTilePosBottomLeft.Y + quadSize.Y);

                // Fill up the target spacing area with similar pixels
                if (renderInput.TargetTileMargin > 0)
                {
                    FillTileSpacing(target.PixelData, renderInput.TargetTileMargin, targetContentPos, renderInput.SourceTileSize);
                }

                // Add an entry to the generated atlas
                Rect atlasRect = new Rect(
                    targetTilePos.X + renderInput.TargetTileMargin,
                    targetTilePos.Y + renderInput.TargetTileMargin,
                    geometry.TargetTileAdvance.X - renderInput.TargetTileMargin * 2,
                    geometry.TargetTileAdvance.Y - renderInput.TargetTileMargin * 2);
                target.Atlas.Add(atlasRect);

                // Advance the target tile position
                targetTilePos.X += geometry.TargetTileAdvance.X;
                if (targetTilePos.X + geometry.TargetTileAdvance.X > target.PixelData.Width)
                {
                    targetTilePos.X  = 0;
                    targetTilePos.Y += geometry.TargetTileAdvance.Y;
                }
            }

            // Update which tiles are considered visually empty
            for (int tileIndex = 0; tileIndex < this.outputTileCount; tileIndex++)
            {
                // Determine target positions on pixel data / buffer
                Rect targetRect = target.Atlas[tileIndex];

                // Update whether the tile is considered visually empty
                if (this.tiles.Data[tileIndex].IsVisuallyEmpty)
                {
                    bool isLayerVisuallyEmpty = IsCompletelyTransparent(
                        target.PixelData,
                        (Point2)targetRect.Pos,
                        (Point2)targetRect.Size);
                    if (!isLayerVisuallyEmpty)
                    {
                        this.tiles.Data[tileIndex].IsVisuallyEmpty = false;
                    }
                }
            }

            return(target);
        }
Пример #6
0
        /// <summary>
        /// Generates pixel and atlas data for a single <see cref="Tileset"/> visual layer.
        /// </summary>
        /// <param name="renderInput"></param>
        /// <param name="sourceData"></param>
        /// <param name="geometry"></param>
        /// <param name="tileData"></param>
        /// <returns></returns>
        private LayerPixelData GenerateLayerPixelData(TilesetRenderInput renderInput, PixelData sourceData, LayerGeometry geometry, RawList <TileInfo> tileData)
        {
            // Create a buffer for writing target pixel data
            LayerPixelData target;

            target.PixelData = new PixelData(geometry.TargetTextureSize.X, geometry.TargetTextureSize.Y);
            target.Atlas     = new List <Rect>();

            // Iterate over tiles and move each tile from source to target
            Point2 targetTilePos = new Point2(0, 0);

            for (int tileIndex = 0; tileIndex < geometry.SourceTileCount; tileIndex++)
            {
                // Initialize a new tile info when necessary
                if (tileIndex >= tileData.Count)
                {
                    tileData.Count++;
                    tileData.Data[tileIndex].IsVisuallyEmpty = true;
                }

                // Determine where on the source buffer the tile is located
                Point2 sourceTilePos = new Point2(
                    geometry.SourceTileAdvance.X * (tileIndex % geometry.SourceTilesPerRow),
                    geometry.SourceTileAdvance.Y * (tileIndex / geometry.SourceTilesPerRow));

                // Draw the source tile onto the target buffer, including its spacing / border
                Point2 targetContentPos = new Point2(
                    targetTilePos.X + renderInput.TargetTileMargin,
                    targetTilePos.Y + renderInput.TargetTileMargin);
                sourceData.DrawOnto(target.PixelData,
                                    BlendMode.Solid,
                                    targetContentPos.X,
                                    targetContentPos.Y,
                                    renderInput.SourceTileSize.X,
                                    renderInput.SourceTileSize.Y,
                                    sourceTilePos.X,
                                    sourceTilePos.Y);

                // Fill up the target spacing area with similar pixels
                if (renderInput.TargetTileMargin > 0)
                {
                    FillTileSpacing(target.PixelData, renderInput.TargetTileMargin, targetContentPos, renderInput.SourceTileSize);
                }

                // Update whether the tile is considered visually empty
                if (tileData.Data[tileIndex].IsVisuallyEmpty)
                {
                    bool isLayerVisuallyEmpty = IsCompletelyTransparent(
                        sourceData,
                        sourceTilePos,
                        renderInput.SourceTileSize);
                    if (!isLayerVisuallyEmpty)
                    {
                        tileData.Data[tileIndex].IsVisuallyEmpty = false;
                    }
                }

                // Add an entry to the generated atlas
                Rect atlasRect = new Rect(
                    targetTilePos.X + renderInput.TargetTileMargin,
                    targetTilePos.Y + renderInput.TargetTileMargin,
                    geometry.TargetTileAdvance.X - renderInput.TargetTileMargin * 2,
                    geometry.TargetTileAdvance.Y - renderInput.TargetTileMargin * 2);
                target.Atlas.Add(atlasRect);

                // Advance the target tile position
                targetTilePos.X += geometry.TargetTileAdvance.X;
                if (targetTilePos.X + geometry.TargetTileAdvance.X > target.PixelData.Width)
                {
                    targetTilePos.X  = 0;
                    targetTilePos.Y += geometry.TargetTileAdvance.Y;
                }
            }

            return(target);
        }