Laden...

System.Drawing - Umschließendes Rechteck von rotiertem Text ermitteln

Erstellt von Waschbecken vor 12 Jahren Letzter Beitrag vor 12 Jahren 2.381 Views
W
Waschbecken Themenstarter:in
799 Beiträge seit 2004
vor 12 Jahren
System.Drawing - Umschließendes Rechteck von rotiertem Text ermitteln

Hallo zusammen,

ich erstelle Grafiken, die lediglich Text enthalten. Die Größe der Grafiken bestimmt sich dabei aus dem Text. Momentan lese ich also mittels Graphics.MeasureString() die Größe aus, erstelle das Bitmap und zeichne dann mittels Graphics.DrawString() den Text darauf. Soll der Text rotiert werden, nehme ich das fertige Bitmap und drehe es.

Das wäre an sich zwar sicher nicht der performanteste, aber doch ein gangbarer Weg. Mein Problem ist allerdings, dass ich eine begrenzte Farbpalette zur Verfügung habe und somit jegliches Antialiasing (was neue Farben erzeugt) vermeiden muss.

Deshalb verwende ich beim Rotieren InterpolationMode.NearestNeighbor. Das funktioniert zwar, erzeugt aber ein ziemlich bescheidenes Ergebnis. Daher mein Gedanke: sollte es nicht möglich sein den Text normal zu zeichnen und mittels Graphics.RotateTransform() zu drehen?

(Der Unterschied ist zwar gering aber doch deutlich sichtbar, ich habe Beides schon verglichen.)

Ich bekomme es momentan nicht hin ohne mit dem Text irgendwo über die Grenzen des Rechtecks zu laufen, d.h. Text abzuschneiden (ich muss die finale Größe des Bitmaps ja kennen und festlegen, bevor ich den Text zeichnen kann).

Hat jemand eine Idee, wie das zu lösen wäre?

C
2.121 Beiträge seit 2010
vor 12 Jahren

Ich versteh die Frage ehrlich gesagt nicht ganz. Erklär am besten nochmal wo du welche Probleme hast.

sollte es nicht möglich sein den Text normal zu zeichnen und mittels Graphics.RotateTransform() zu drehen?

Ich dachte das machst du bereits so? Oder meinst du in deinem Satz vorher das komplette Bild, statt nur den einen Text? Es müsste doch auch gehen, den Text gedreht in ein Bild zu zeichnen und das dann mit transparentem Hintergrund in dein endgültiges Bild zu setzen.

Ich bekomme es momentan nicht hin ohne mit dem Text irgendwo über die Grenzen des Rechtecks zu laufen, d.h. Text abzuschneiden (ich muss die finale Größe des Bitmaps ja kennen und festlegen, bevor ich den Text zeichnen kann).

Das ist dann ja wieder eine ganz andere Frage, da müsstest du mit Trigonometrie die wirklich benötigte Größe des Bilds ausrechnen.

W
Waschbecken Themenstarter:in
799 Beiträge seit 2004
vor 12 Jahren

Erklär am besten nochmal wo du welche Probleme hast.

Vorgehensweise jetzt:1.Ich messe die Größe des Strings aus. 1.Ich erstelle mit der Größe ein Bitmap. 1.Ich zeichne den Text auf das Bitmap. 1.Wenn der Text rotiert werden soll, rotiere ich das gesamte bereits erzeugte Bitmap.

Das Problem: die Qualität des Textes ist schlecht, da ich zur Verwendung von InterpolationMode.NearestNeighbor gezwungen bin, da ich kein Antialiasing zulassen darf.

Daher der Gedanke das Ganze umzustellen und den Text gleich gedreht zu zeichnen. Das Problem: ich muss vorher bereits die Größe des finalen Bitmaps bekommen, auf das dann gezeichnet wird.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo Waschbecken,

Ich bekomme es momentan nicht hin ohne mit dem Text irgendwo über die Grenzen des Rechtecks zu laufen

zwar kann man sicher per Trigonometrie ausrechnen, wieviel Platz das Rechteck, das den Text einschließt, brauchen würde, wenn man es dreht. Aber das sagt ja nichts darüber aus, wieviel Platz der eigentliche Text nach dem Drehen braucht, weil das von der genauen Form insbesondere des ersten und des letzten Buchstaben abhängt. Und das ist ja wohl genau das Problem.

ich muss vorher bereits die Größe des finalen Bitmaps bekommen, auf das dann gezeichnet wird.

Diese Annahme würde ich in Frage stellen. Es ist nicht nötig, die finale Größe von Anfang an zu kennen. Du kannst es so machen, wie ich es in MeasureString und Alternativen [Minimales einschließendes Rechteck der Schrift ermitteln] beschreiben habe. Dass der Text bei dir zusätzlich noch gedreht ist, ändert ja nichts am Prinzip.

herbivore

W
Waschbecken Themenstarter:in
799 Beiträge seit 2004
vor 12 Jahren

Hallo herbivore,

mein Problem ist allerdings tatsächlich herauszufinden, wie groß das Rechteck ist, in dem sich der Text befindet, nachdem er gedreht wurde. Siehe Anhang - ich benötige die Größe für B, nicht für A (A bekomme ich ja z.B. auf deinem Weg).

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo Waschbecken,

wenn du mit A und B tatsächlich genau das rote und blaue Rechteck meinst und deren Verhältnis zueinander so ist, wie grafisch dargestellt, also dass die Ecken von A die Kanten von B berühren sollen, dann kann man aus A durch einfache Trigonometrie B ausrechnen. Da sehe ich kein Problem. Man kann muss nur auf die Dreiecke achten, von denen man immer eine Kante und zwei Winkel kennt, also alle anderen Kanten (und Winkel) ausrechnen kann.

EDIT: Noch einfacher geht es, wenn man die Punkte des Rechtecks A mit Graphics.TransformPoints dreht und anschließend die minimalen und maximalen X- und Y-Koordinaten der gedrehten Punkte ermittelt.

A bekomme ich ja z.B. auf deinem Weg

Meinen Weg kann man aber auch - natürlich etwas angepasst - verwenden, um das minimale umschließende Rechteck des gedrehten Textes auszurechnen. So hatte ich das gemeint, denn ich bin bisher davon ausgegangen, dass du genau das willst.

herbivore

W
Waschbecken Themenstarter:in
799 Beiträge seit 2004
vor 12 Jahren

Ok, ich habe es gelöst (ob das nun "der" Weg nach Rom ist, sei mal dahingestellt). Nachfolgend ein komplettes, ausführbares Beispiel (als ASP.NET MVC-Controller).

public class ImageController : Controller
    {

        public ActionResult Test()
        {

            var text = DateTime.Now.ToString();
            var font = new Font("Arial", 20, FontStyle.Regular);
            var angle = 233;

            SizeF textSize = GetEvenTextImageSize(text, font);

            SizeF imageSize;
            
            if (angle == 0)
                imageSize = textSize;
            else
                imageSize = GetRotatedTextImageSize(textSize, angle);

            using (var canvas = new Bitmap((int)imageSize.Width, (int)imageSize.Height))
            {

                using(var graphics = Graphics.FromImage(canvas))
                {

                    graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    graphics.SmoothingMode = SmoothingMode.HighQuality;
                    graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;

                    SizeF textContainerSize = graphics.VisibleClipBounds.Size;
                    graphics.TranslateTransform(textContainerSize.Width / 2, textContainerSize.Height / 2);
                    graphics.RotateTransform(angle);

                    graphics.DrawString(text, font, Brushes.Black, -(textSize.Width / 2), -(textSize.Height / 2));

                }

                var stream = new MemoryStream();
                canvas.Save(stream, ImageFormat.Png);
                stream.Seek(0, SeekOrigin.Begin);
                return new FileStreamResult(stream, "image/png");

            }

        }

        private static SizeF GetEvenTextImageSize(string text, Font font)
        {
            using (var image = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
            {
                using (Graphics graphics = Graphics.FromImage(image))
                {
                    return graphics.MeasureString(text, font);
                }
            }
        }

        private static SizeF GetRotatedTextImageSize(SizeF fontSize, int angle)
        {

            // Source: http://www.codeproject.com/KB/graphics/rotateimage.aspx

            double theta = angle * Math.PI / 180.0;

            while (theta < 0.0)
                theta += 2 * Math.PI;

            double adjacentTop, oppositeTop;
            double adjacentBottom, oppositeBottom;

            if ((theta >= 0.0 && theta < Math.PI / 2.0) || (theta >= Math.PI && theta < (Math.PI + (Math.PI / 2.0))))
            {
                adjacentTop = Math.Abs(Math.Cos(theta)) * fontSize.Width;
                oppositeTop = Math.Abs(Math.Sin(theta)) * fontSize.Width;
                adjacentBottom = Math.Abs(Math.Cos(theta)) * fontSize.Height;
                oppositeBottom = Math.Abs(Math.Sin(theta)) * fontSize.Height;
            }
            else
            {
                adjacentTop = Math.Abs(Math.Sin(theta)) * fontSize.Height;
                oppositeTop = Math.Abs(Math.Cos(theta)) * fontSize.Height;
                adjacentBottom = Math.Abs(Math.Sin(theta)) * fontSize.Width;
                oppositeBottom = Math.Abs(Math.Cos(theta)) * fontSize.Width;
            }

            int nWidth = (int)Math.Ceiling(adjacentTop + oppositeBottom);
            int nHeight = (int)Math.Ceiling(adjacentBottom + oppositeTop);

            return new SizeF(nWidth, nHeight);

        }

    }