629 / 41 / 25
Регистрация: 25.04.2017
Сообщений: 510
1

Простой Paint - Разбор полетов

16.12.2018, 17:17. Показов 5874. Ответов 53
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Здравствуйте, задумал я написать простой растровый графический редактор на подобии Paint.
Совершенствовать модель буду со временем, а пока начал с простых вещей.
Есть несколько вопросов:
  1. Как и где лучше хранить то, что рисуется
  2. Как масштабировать холст вместе с тем, что рисуется
В качестве холста использую picturebox
C#
1
2
3
4
5
 btm = new Bitmap(Canvas.Width, Canvas.Height);
            Canvas.Image = btm;
            //Создаем область для работы с графикой на элементе Canvas.Image
            g = Graphics.FromImage(Canvas.Image);            
            g.FillRectangle(Brushes.White, 0, 0, btm.Width, btm.Height);
Возникли проблемы с сохранением
C#
1
2
3
4
5
6
7
        private void btSave_Click(object sender, EventArgs e)
        {
            var sfd = new SaveFileDialog() { Filter = "Image|*.png;*.bmp;*.jpg", DefaultExt = ".png" };
            if (sfd.ShowDialog() == DialogResult.OK)
                using (var bmp = Canvas.Image)
                    bmp.Save(sfd.FileName);//Сораняем рисунок
        }
После сохранения и повторного рисования возникает ошибка "Неверный параметр", как исправить?

Полный текст программы
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace PaintBoard
{
    public partial class MainForm : Form
    {
        Color color = Color.Black; //Создаем переменную типа Color присваиваем ей черный цвет.
        bool isPressed = false; //логическая переменная понадобиться для опеределения когда можно рисовать на Canvas
        Point CurrentPoint; //Текущая точка ресунка.
        Point PrevPoint; //Это начальная точка рисунка.
        Graphics g; //Создаем графический элемент.
        ColorDialog colorDialog = new ColorDialog(); //диалоговое окно для выбора цвета.
        private Bitmap btm;
 
        public MainForm()
        {
            InitializeComponent();
            //Canvas.BackColor = Color.White;
            Panel1.BackColor = Color.Gray;
            lbColor.BackColor = Color.Black; //По умолчанию для пера задан черный цвет, поэтому мы зададим такой же фон для label2
            btm = new Bitmap(Canvas.Width, Canvas.Height);
            Canvas.Image = btm;
            //Создаем область для работы с графикой на элементе Canvas.Image
            g = Graphics.FromImage(Canvas.Image);            
            g.FillRectangle(Brushes.White, 0, 0, btm.Width, btm.Height);
            g.SmoothingMode = SmoothingMode.AntiAlias;
 
 
 
        }
 
        private void lbColor_Click(object sender, EventArgs e)
        {
            if (colorDialog.ShowDialog() == DialogResult.OK) //Если окно закрылось с OK, то меняем цвет для пера и фона lbColor
            {
                color = colorDialog.Color; //меняем цвет для пера
                lbColor.BackColor = colorDialog.Color; //меняем цвет для Фона lbColor
            }
        }
 
        private void Canvas_MouseUp(object sender, MouseEventArgs e)
        {
            isPressed = false;//Запрещаем рисовать
        }
 
        private void Canvas_MouseDown(object sender, MouseEventArgs e)
        {
            isPressed = true;// Разрешаем рисовать
            CurrentPoint = e.Location;// Начальная точка равна текущему положению на Canvas
 
        }
 
        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            Canvas.Cursor = Cursors.Cross;
            ssPosXY.Text = e.Location.ToString();
            if (isPressed)
            {
 
                PrevPoint = CurrentPoint;// Наносим первую точку на Canvas
                CurrentPoint = e.Location;// Наносим вторую точку на Canvas
                my_Pen();// Соединяем 2 точки линией
                Canvas.Invalidate();
            }
        }
 
        private void btClear_Click(object sender, EventArgs e)
        {
            g.Clear(Color.White);
            Canvas.Invalidate();
            //Очищает элемент Canvas
        }
        private void my_Pen()
        {
            Pen pen = new Pen(color, (float)numericUpDown1.Value); //Создаем перо, задаем ему цвет и толщину.
            pen.StartCap = pen.EndCap = LineCap.Round;
            g.DrawLine(pen, CurrentPoint, PrevPoint); //Соединияем точки линиями
        }
 
        private void Canvas_MouseLeave(object sender, EventArgs e)
        {
            ssPosXY.Text = "";
        }
 
 
        private void btSave_Click(object sender, EventArgs e)
        {
            var sfd = new SaveFileDialog() { Filter = "Image|*.png;*.bmp;*.jpg", DefaultExt = ".png" };
            if (sfd.ShowDialog() == DialogResult.OK)
                using (var bmp = Canvas.Image)
                    bmp.Save(sfd.FileName);//Сораняем рисунок
        }
    }
}
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
16.12.2018, 17:17
Ответы с готовыми решениями:

PropertyGrid разбор полетов использования
Добрый день. Есть классы который я изменить не могу. Есть достаточно удобный для редактирования...

Разбор "полетов" по потоку Thread
наткнулся на статейку на сайте https://habr.com/company/nixsolutions/blog/260745/ и у меня...

IComparer. Разбор полетов
Нигде не нашел толкового описания, как именно работает это дело, как расставляет приоритеты и т.п....

Простой векторный графический редактор (разбор полётов)
Всем привет! Меня зовут ashsvis и я программист... Я пытался бороться с этим "недугом", но эта...

53
916 / 497 / 201
Регистрация: 08.10.2018
Сообщений: 1,541
Записей в блоге: 11
03.07.2019, 11:48 41
Author24 — интернет-сервис помощи студентам
Цитата Сообщение от Storm23 Посмотреть сообщение
для ластика тоже должна поддерживаться полупрозрачность (opacity) и hardness
Жесть! Мы ведь пишем "Простой Paint"! Буду теперь искать и читать про hardness, что это и зачем он нужен.
0
Эксперт .NETАвтор FAQ
10409 / 5139 / 1824
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
03.07.2019, 13:51 42
Цитата Сообщение от ashsvis Посмотреть сообщение
Жесть! Мы ведь пишем "Простой Paint"!
Простой Paint писал worldandlife, а не вы
Только такой простой паинт никому нафик не нужен, кроме как в образовательных целях. Если уж писать - так что-то нормальное.
0
916 / 497 / 201
Регистрация: 08.10.2018
Сообщений: 1,541
Записей в блоге: 11
03.07.2019, 17:51 43
Нашёл, что такое "hardness":
Простой Paint - Разбор полетов

По нашему, это степень размытости линии карандаша (и вообще контура). Такое, конечно, средствами graphics.DrawLines() не сделать. Тут нужен другой подход, ну а какой, "будем искать (с)".

Теперь о ластике. Ластик устанавливает прозрачность там, где оставлен его цвет. Так как применение действия ластика происходит, когда кнопка мыши отпускается, то используется такой механизм:
1. Пока левая кнопка не отпущена, линия ластика рисуется цветом, который выбран для ластика.
2. Когда левая кнопка отпускается, то выполняется метод doc.Layer.MakeTransparent(RibberStyle.Color);,
в котором цвет ластика заменяется на прозрачный (alpha-канал устанавливается в ноль).
Конечно, скажете вы, сотрётся также и цвет, который есть на картинке... Да, это так. "Многое ещё не доработано (с)". Поэтому, можно выбрать цвет ластика из тех, которых нет на картинке.

Вот такое, временное решение.
Репозиторий обновил.
0
1514 / 905 / 328
Регистрация: 17.05.2015
Сообщений: 3,417
04.07.2019, 11:14 44
Цитата Сообщение от ashsvis Посмотреть сообщение
Такое, конечно, средствами graphics.DrawLines() не сделать
я думаю, надо самому написать DrawLine, который будет вручную закрашивать пиксели с нужным значением альфа канала
0
916 / 497 / 201
Регистрация: 08.10.2018
Сообщений: 1,541
Записей в блоге: 11
05.07.2019, 13:20 45
Цитата Сообщение от Рядовой Посмотреть сообщение
самому написать DrawLine
Примерчик запостите здесь?
0
384 / 184 / 107
Регистрация: 07.01.2016
Сообщений: 496
05.07.2019, 13:30 46
Цитата Сообщение от ashsvis Посмотреть сообщение
цвет ластика
есть известное решение - текстура прямоугольниками
Миниатюры
Простой Paint - Разбор полетов  
0
1514 / 905 / 328
Регистрация: 17.05.2015
Сообщений: 3,417
05.07.2019, 14:23 47
Цитата Сообщение от ashsvis Посмотреть сообщение
Примерчик запостите здесь?
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class LineRenderer
    {
        public LineRenderer(double X1, double Y1, double X2, double Y2, double tikness)
        {
            x1 = X1; x2 = X2; y1 = Y1; y2 = Y2;
            Tikness = tikness;
            k = (Y2 - Y1) / (X2 - X1);
            b = Y1 - k * X1;
        }
        double x1, x2, y1, y2;
        double k, b, Tikness;
        public bool IsPointOnLine(int X, int Y)
        {
            double dist = DistanceToSegment(x1, y1, x2, y2, X, Y);
            if (dist <= Tikness) return true;
            else return false;
        }
        public Bitmap Render(Color color, int hardness)
        {
            Bitmap bmp = new Bitmap(1200, 1200);
            for (int i = 0; i < bmp.Width; i++)
                for (int j = 0; j < bmp.Height; j++)
                {
                    if (IsPointOnLine(i, j))
                        bmp.SetPixel(i, j, Color.FromArgb(255 / 100 * hardness, color));
                }
            return bmp;
        }
        public double DistanceToSegment(double ax, double ay, double bx, double by, double x, double y)
        {
            double ak = Math.Sqrt((x - ax) * (x - ax) + (y - ay) * (y - ay));
            double kb = Math.Sqrt((x - bx) * (x - bx) + (y - by) * (y - by));
            double ab = Math.Sqrt((ax - bx) * (ax - bx) + (ay - by) * (ay - by));
 
            double mulScalarAKAB = (x - ax) * (bx - ax) + (y - ay) * (by - ay);
            double mulScalarBKAB = (x - bx) * (-bx + ax) + (y - by) * (-by + ay);
 
 
            if (ab == 0) return ak;
 
            else if (mulScalarAKAB >= 0 && mulScalarBKAB >= 0)
            {
 
                double p = (ak + kb + ab) / 2.0;
                double s = Math.Sqrt(Math.Abs((p * (p - ak) * (p - kb) * (p - ab))));
 
                return (2.0 * s) / ab;
            }
 
            else if (mulScalarAKAB < 0 || mulScalarBKAB < 0)
            {
                return Math.Min(ak, kb);
            }
 
            else return 0;
 
        }
    }
C#
1
2
3
4
        protected override void OnPaint(PaintEventArgs e)
        {
            e.Graphics.DrawImage(new LineRenderer(20, 120, 200, 120, 15).Render(Color.Blue, 30), 0, 0);
        }
Можно добавить изменение альфа канала в зависимости от того, как далеко точка находится от прямой
0
916 / 497 / 201
Регистрация: 08.10.2018
Сообщений: 1,541
Записей в блоге: 11
05.07.2019, 14:51 48
Цитата Сообщение от Рядовой Посмотреть сообщение
добавить изменение альфа канала
Спасибо за пример, надо не спеша посмотреть.

По поводу альфа канала: модель редактора такая, что применение прозрачности к границам линии происходит
в результирующем Image, то есть делается прозрачным и край новой линии и то, что было уже нарисовано до этого,
а надо, чтобы край новой линии смешивался с предыдущим рисунком (учитывался фон).

Я пробовал делать линию последовательно в этой программе:
1. Самая толстая линия (требуемой ширины) с маленьким значением альфа
2. Потом линия чуть уже и альфа канал чуть больше и т. д.
то получается прозрачность добавляется ко всему контуру линии, делая прозрачным и предыдущее нарисованное.

Вывод: нужно новую линию рисовать на отдельном холсте, а потом накладывать этот холст на то, что было нарисовано ранее.
Цитата Сообщение от alexus5 Посмотреть сообщение
есть известное решение
Картинка красивая, где исходник посмотреть?
0
384 / 184 / 107
Регистрация: 07.01.2016
Сообщений: 496
06.07.2019, 07:33 49
Цитата Сообщение от ashsvis Посмотреть сообщение
Картинка красивая, где исходник посмотреть
исходники сырые пока, по сути - там TextureBrush + LinearGradientBrush с прозрачными цветами
0
1514 / 905 / 328
Регистрация: 17.05.2015
Сообщений: 3,417
06.07.2019, 10:49 50
ashsvis, вот немного доработал.
C#
1
2
3
4
5
protected override void OnPaint(PaintEventArgs e)
        {
            e.Graphics.DrawImage(new LineRenderer(20, 60, 200, 60, 15).Render(Color.Blue, 10), 0, 0);
            e.Graphics.DrawImage(new LineRenderer(20, 120, 200, 120, 15).Render(Color.Blue, 90), 0, 0);
        }
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    class LineRenderer
    {
        public LineRenderer(double X1, double Y1, double X2, double Y2, double tikness)
        {
            x1 = X1; x2 = X2; y1 = Y1; y2 = Y2;
            Tikness = tikness;
            k = (Y2 - Y1) / (X2 - X1);
            b = Y1 - k * X1;
        }
        double x1, x2, y1, y2;
        double k, b, Tikness;
        private bool IsPointOnLine(int X, int Y, out double distnPercent)
        {
            double dist = DistanceToSegment(x1, y1, x2, y2, X, Y);
            distnPercent = DistanseAbs(dist, Tikness);
            if (dist < Tikness)
                return true;
            else return false;
        }
        public Bitmap Render(Color color, int hardness)
        {
            Bitmap bmp = new Bitmap(1000, 1000);
            for (int i = 0; i < bmp.Width; i++)
                for (int j = 0; j < bmp.Height; j++)
                {
                    double DistnancePercent;
                    if (IsPointOnLine(i, j, out DistnancePercent))
                    {
                        int alpha = AlphaFromHardness(DistnancePercent, hardness);
                        bmp.SetPixel(i, j, Color.FromArgb(alpha, color));
                    }
                }
            return bmp;
        }
        private double DistanseAbs(double dist, double tick) => Math.Abs((dist - tick) / ((dist + tick) / 2)) * 100;
        private int AlphaFromHardness(double distPercent, int hardness)
        {
            int res = (int)(255 / 100 * distPercent) * hardness / 10;
            if (res > 255) return 255;
            else if (res < 0) return 0;
            return res;
        }
        public double DistanceToSegment(double ax, double ay, double bx, double by, double x, double y)
        {
            double ak = Math.Sqrt((x - ax) * (x - ax) + (y - ay) * (y - ay));
            double kb = Math.Sqrt((x - bx) * (x - bx) + (y - by) * (y - by));
            double ab = Math.Sqrt((ax - bx) * (ax - bx) + (ay - by) * (ay - by));
 
            double mulScalarAKAB = (x - ax) * (bx - ax) + (y - ay) * (by - ay);
            double mulScalarBKAB = (x - bx) * (-bx + ax) + (y - by) * (-by + ay);
 
 
            if (ab == 0) return ak;
 
            else if (mulScalarAKAB >= 0 && mulScalarBKAB >= 0)
            {
 
                double p = (ak + kb + ab) / 2.0;
                double s = Math.Sqrt(Math.Abs((p * (p - ak) * (p - kb) * (p - ab))));
 
                return (2.0 * s) / ab;
            }
 
            else if (mulScalarAKAB < 0 || mulScalarBKAB < 0)
            {
                return Math.Min(ak, kb);
            }
 
            else return 0;
 
        }
    }
Изображения
  
1
916 / 497 / 201
Регистрация: 08.10.2018
Сообщений: 1,541
Записей в блоге: 11
12.07.2019, 18:24 51
Интегрировал предложенный класс LineRenderer для проверки. Результаты не очень...
Конечно, класс заточен под прямые линии, плюс - ресурсы потребляются немерено.
Так что в таком виде это употребить невозможно, но, как пример - интересно.

Текущее состояние смотрите в репозитории https://github.com/ashsvis/my-first-Paint
0
1514 / 905 / 328
Регистрация: 17.05.2015
Сообщений: 3,417
15.07.2019, 20:41 52
ashsvis, я попытался сделать кисть. Вместо setpixel использовал небезопасный класс
Аналоги для GetPixel/SetPixel
для расчетов использовал только те пиксели, которые попадают в зону отрисовки...
Но увы, скорость очень низкая и совсем не подходит для ваших целей
ну оно и понятно, для каждого пикселя рассчитать расстояние, перевести в проценты затем в значение альфа канала и все это за доли секунды - трудно..
1
1514 / 905 / 328
Регистрация: 17.05.2015
Сообщений: 3,417
27.11.2020, 22:58 53
ashsvis, Привет! Надеюсь, вы еще не забросили свой редактор =)
хз, почему в тот раз я привязался к линиям, но немного скорректировал класс, и оно заработало мгновенно, и это с тормозящими get/set pixel
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.DoubleBuffered = true;
            Canvas = new Bitmap(500, 500);
            BrushPoint = new PointBrush() { color = Color.Orange, Tikness = 11, Hardness = 15 };
            trackBar1.Minimum = 0;
            trackBar1.Maximum = 100;
            trackBar1.Value = BrushPoint.Hardness;
            trackBar1.ValueChanged += new EventHandler((object s, EventArgs e) => BrushPoint.Hardness = trackBar1.Value);
        }
        private Bitmap Canvas;
        private bool IsDraw = false;
        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            IsDraw = true;
            BrushPoint.Render(e.Location, Canvas);
            Invalidate();
        }
        PointBrush BrushPoint;
        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            IsDraw = false;
        }
 
        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            if (IsDraw)
            {
                BrushPoint.Render(e.Location, Canvas);
                Invalidate();
            }
        }
        protected override void OnPaint(PaintEventArgs e)
        {
            e.Graphics.DrawImage(Canvas, 0, 0);
        }
 
    }
    public class PointBrush
    {
        public Color color { get; set; }
        public float X { get; set; }
        public float Y { get; set; }
        public float Tikness { get; set; }
        public int Hardness { get; set; }
        private float GetDistance(float x1, float y1)
        {
            return (float)Math.Sqrt(Math.Pow((X - x1), 2) + Math.Pow((Y - y1), 2));
        }
        private float DistanseAbs(float dist) => Math.Abs((dist - Tikness) / ((dist + Tikness) / 2)) * 100;
        public void Render(Point p, Bitmap bitmap)
        {
            X = p.X; Y = p.Y;
            float LocalDist, DistPersent;
            for (int i = (int)(X - Tikness); i < (int)(X + Tikness) && i < bitmap.Width; i++)
                for (int j = (int)(Y - Tikness); j < (int)(Y + Tikness) && j < bitmap.Height; j++)
                {
                    LocalDist = GetDistance(i, j);
                    if (LocalDist < Tikness)
                    {                       
                        Color def = bitmap.GetPixel(i, j);
                        DistPersent = DistanseAbs(LocalDist);
                        int alpha = AlphaFromHardness(DistPersent);
                        if (alpha < def.A) continue;
                        bitmap.SetPixel(i, j, Color.FromArgb(alpha, color));
                    }
                }
        }
        private int AlphaFromHardness(float distPercent)
        {
            int res = (int)(255 / 100 * distPercent) * Hardness / 30;
            return res < 0 ? 0 : res > 255 ? 255 : res;
        }
    }
Миниатюры
Простой Paint - Разбор полетов  
0
1514 / 905 / 328
Регистрация: 17.05.2015
Сообщений: 3,417
27.11.2020, 23:07 54
Вообще, оно и сейчас будет тормозить, если делать оочень толстую кисть.
В строка 58-59 я взял с запасом диапозон пикселей от х-tikness до х+tikness , хотя достаточно и tikness/2
Ну и get set заменить на что то приличное надо
0
27.11.2020, 23:07
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
27.11.2020, 23:07
Помогаю со студенческими работами здесь

Открыть картинку в Paint или Paint Net если установлен
Здравствуйте. Как можно открыть картинку в программе Paint или если существует в Paint net И еще...

Разбор полетов
Меня больше всего интересует строчка вычесления позиции символа. (ring - lotr + 1). объясните,...

разбор полетов, ассемблер..
ПРивет всем! ТОлько начал изучать ассемблер, есть куча непоняток. Решил начинать с примеров, если...

Разбор полетов. ошибка е2140
Всем Привет! Тут такая тема- написать программу для задачи: Расчитать вероятность попадания...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
54
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru