Как нарисовать дугу на WritableBitmap + WritablebitmapEx

1

Я рисую много фигур на WritableBitmap с помощью WritableBitmapEx в моем приложении WPF. К сожалению, нет никакой возможности перейти к функции, чтобы нарисовать дугу.

Как я могу:

1. Нарисуйте дугу на WritableBitmap?

2. Нарисуйте сглаженную дугу с переменной толщиной на WritableBitmap?

Мне просто нужно нарисовать круговые дуги.

Существует возможность нарисовать красивую, сглаженную дугу с переменной толщиной (System.Windows.Media.ArcSegment) на Canvas - но с тысячами фигур производительность Canvas оставляет желать лучшего - вот почему я использую WritableBitmap.

Если это понадобится для некоторых алгоритмов, я уже рассчитал параметры дуги, такие как: CenterPoint, Radius, StartPoint, EndPoint, StartAngle, EndAngle, ArcLength, IsLarge или Direction

Я пытался нарисовать его вручную с кодом, подобным этому:

int number_of_points = 1000;
for(int i=0; i<=number_of_points; i++){
    double progress=(double)i/number_of_points;
    double theta = (StartAngle + ArcLength * progress) * Math.PI / 180.0;
    draw_pixel(
        Center.X + Radius * Math.Cos(theta),
        Center.Y + Radius * Math.Sin(theta)
    );
}

но с разным разрешением изображения, варьирующимся размером дуги (как рассчитать оптимальное число_значений?), различной толщиной дуги и с помощью сглаживания, он начинает немного сложнее.

  • 0
    Рассматривали ли вы преобразование в System.Drawing.Bitmap, использование System.Drawing.Graphics для выполнения функций рисования, в которых также есть параметры для сглаживания, а затем обратное преобразование? Я думаю, что, возможно, есть более эффективный способ, но это сработает.
  • 0
    Нет, не знаю Есть ли простой способ нарисовать дугу на System.Drawing.Bitmap?
Показать ещё 2 комментария
Теги:
wpf
antialiasing
writablebitmap

1 ответ

0
Лучший ответ

1. Нарисуйте дугу на WritableBitmap?

После анализа источников mono libgdiplus на github я обнаружил, что они рисуют дугу, используя кривую Безье.

Я портировал некоторые из своих функций на С#. Функция расширения DrawArc может использоваться (с помощью DrawBezier из WritableBitmapEx) для рисования простой дуги. В WritableBitmapEx нет сглаженной версии DrawBezier, поэтому это решение отвечает (только) на мой первый вопрос:

namespace System.Windows.Media.Imaging
{
    public static partial class WriteableBitmapArcExtensions
    {
        //port of mono libgdiplus function
        //append_arcs (GpPath *path, float x, float y, float width, float height, float startAngle, float sweepAngle)
        //from: https://github.com/mono/libgdiplus/blob/master/src/graphics-path.c
        public static void DrawArc(this WriteableBitmap bmp, float x, float y, float width, float height, float startAngle, float sweepAngle, Color color)
        {
            int i;
            float drawn = 0;
            int increment;
            float endAngle;
            bool enough = false;

            if (Math.Abs(sweepAngle) >= 360)
            {
                bmp.DrawEllipse((int)x, (int)y, (int)width, (int)height, color);
                return;
            }

            endAngle = startAngle + sweepAngle;
            increment = (endAngle < startAngle) ? -90 : 90;

            /* i is the number of sub-arcs drawn, each sub-arc can be at most 90 degrees.*/
            /* there can be no more then 4 subarcs, ie. 90 + 90 + 90 + (something less than 90) */
            for (i = 0; i < 4; i++)
            {
                float current = startAngle + drawn;
                float additional;

                if (enough)
                    return;

                additional = endAngle - current; /* otherwise, add the remainder */
                if (Math.Abs(additional) > 90)
                {
                    additional = increment;
                }
                else
                {
                    /* a near zero value will introduce bad artefact in the drawing */
                    if ((additional >= -0.0001f) && (additional <= 0.0001f))
                        return;
                    enough = true;
                }

                bmp._DrawArc(
                        x, y,
                        width, height, /* bounding rectangle */
                        current, current + additional, color);
                drawn += additional;
            }
        }

        //port of mono libgdiplus function
        //append_arc (GpPath *path, BOOL start, float x, float y, float width, float height, float startAngle, float endAngle)
        //from: https://github.com/mono/libgdiplus/blob/master/src/graphics-path.c
        private static void _DrawArc(this WriteableBitmap bmp, float x, float y, float width, float height, float startAngle, float endAngle, Color color)
        {
            double sin_alpha, sin_beta, cos_alpha, cos_beta;

            var rx = width / 2;
            var ry = height / 2;

            /* center */
            var cx = x + rx;
            var cy = y + ry;

            /* angles in radians */
            var alpha = startAngle * Math.PI / 180;
            var beta = endAngle * Math.PI / 180;

            /* adjust angles for ellipses */
            alpha = Math.Atan2(rx * Math.Sin(alpha), ry * Math.Cos(alpha));
            beta = Math.Atan2(rx * Math.Sin(beta), ry * Math.Cos(beta));

            if (Math.Abs(beta - alpha) > Math.PI)
            {
                if (beta > alpha)
                    beta -= 2 * Math.PI;
                else
                    alpha -= 2 * Math.PI;
            }

            var delta = beta - alpha;
            // http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
            var bcp = 4.0 / 3 * (1 - Math.Cos(delta / 2)) / Math.Sin(delta / 2);

            sin_alpha = Math.Sin(alpha);
            sin_beta = Math.Sin(beta);
            cos_alpha = Math.Cos(alpha);
            cos_beta = Math.Cos(beta);

            /* starting point */
            double sx = cx + rx * cos_alpha;
            double sy = cy + ry * sin_alpha;

            //DrawBezier comes from WritableBitmapEx library
            bmp.DrawBezier(
                    (int)(sx),
                    (int)(sy),
                    (int)(cx + rx * (cos_alpha - bcp * sin_alpha)),
                    (int)(cy + ry * (sin_alpha + bcp * cos_alpha)),
                    (int)(cx + rx * (cos_beta + bcp * sin_beta)),
                    (int)(cy + ry * (sin_beta - bcp * cos_beta)),
                    (int)(cx + rx * cos_beta),
                    (int)(cy + ry * sin_beta),
                    color
            );
        }
    }
}

Я прокомментировал проблему на сайте WritableBitmapEx: я хотел бы рисовать дуги - поэтому часть этого кода будет включена в библиотеку WritableBitmapEx.


2. Нарисуйте сглаженную дугу с переменной толщиной на WritableBitmap?

После прочтения комментария от ForeverZer0 я сделал несколько экспериментов с System.Drawing.Graphics и WritableBitmap. С помощью получения DrawingContext для wpf WriteableBitmap я сделал это с таким кодом:

WritableBitmap ret = BitmapFactory.New(img_width, img_height);

ret.Lock();
var bmp = new System.Drawing.Bitmap(
    ret.PixelWidth,
    ret.PixelHeight,
    ret.BackBufferStride,
    System.Drawing.Imaging.PixelFormat.Format32bppPArgb,
    ret.BackBuffer
);

System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp);

g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;

g.DrawArc(...); //<-- draws an antialiased arc with variable thickness

g.Dispose();
bmp.Dispose();
ret.AddDirtyRect(new Int32Rect(0, 0, ret.PixelWidth, ret.PixelHeight));
ret.Unlock();

return ret; //<-- WritableBitmap with beautifull arc on it;

Ещё вопросы

Сообщество Overcoder
Наверх
Меню