Обрезать правильную часть изображения, пока PictureBox находится в режиме «масштабирования»

У меня есть PictureBox1 с его sizemode, установленным на Stretch и PictureBox1. PictureBox1 содержит изображение, и позвольте мне выбрать его часть, затем обрезать и сохранить обрезанную часть внутри PictureBox2. Он отлично работает когда sizemode установлен на Stretch и изображение не увеличено, но не тогда, когда я увеличиваю его или устанавливаю sizemode для увеличения.

рабочий пример - для sizemode установлено значение «stretch»

Код, который я использую для обрезки части изображения (первоисточник)

try
{
    float stretch1X = 1f * pictureBox1.Image.Width / pictureBox1.ClientSize.Width;
    float stretch1Y = 1f * pictureBox1.Image.Height / pictureBox1.ClientSize.Height;

    Point pt = new Point((int)(_mDown.X * stretch1X), (int)(_mDown.Y * stretch1Y));
    Size sz = new Size((int)((_mCurr.X - _mDown.X) * stretch1X),
                       (int)((_mCurr.Y - _mDown.Y) * stretch1Y));



    if (sz.Width > 0 && sz.Height > 0)
    {
        Rectangle rSrc = new Rectangle(pt, sz);
        Rectangle rDest = new Rectangle(Point.Empty, sz);

        Bitmap bmp = new Bitmap(sz.Width, sz.Height);
        using (Graphics G = Graphics.FromImage(bmp))
            G.DrawImage(pictureBox1.Image, rDest, rSrc, GraphicsUnit.Pixel);
        return bmp;
    }

    return null;
}
catch (Exception ex)
{
    throw ex;
}

Как правильно рассчитать? Как заставить функцию обрезки работать таким образом, чтобы пользователь мог увеличивать/уменьшать масштаб и при этом обрезать правильную часть изображения?

Когда он увеличен, тогда stretch1X == stretch1Y. Смотри сюда.

Hans Passant 29.05.2019 14:03

Как я могу применить это к своему сценарию? Не могли бы вы объяснить немного больше? Ваше объяснение мне не совсем понятно @HansPassant

bapster 29.05.2019 15:36

Вам нужно знать размер отображаемого изображения, этот метод вычисляет его. Просто продублируйте код для случая масштабирования. Как только вы узнаете этот размер, вы можете разделить его на image.Width, чтобы узнать stretch1X. И, таким образом, stretch1Y. Отображаемое изображение будет помещено в рамки по горизонтали или по вертикали со смещением на разницу между размером отображаемого изображения и PictureBox.ClientSize, деленную на 2.

Hans Passant 29.05.2019 15:41

Это немного сбивает с толку, можете ли вы опубликовать код подсказки, основанный на коде, который я разместил выше? Заранее спасибо @HansPassant

bapster 29.05.2019 15:56

@Hans: Ты совершенно прав! Забавно, как я не смог найти этот пост..!? Я оставляю ответ, потому что он также создает обрезанное изображение и демонстрирует, что он также работает в режиме увеличения.

TaW 29.05.2019 16:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
716
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вам нужно рассчитать точки, используя коэффициент растяжения и, возможно, также смещение.

Для Zoom есть только коэффициент один, так как соотношение сторон всегда одинаково для Image и PictureBox, но обычно есть компенсировать; для Stretch вам нужно не смещение, а коэффициенты два.

Вот пример, который полностью использует дваPictureBoxes, два показывают увеличенную версию и обрезанное растровое изображение. Он использует универсальную функцию ImageArea, которая определяет размер и смещение.

Две переменные уровня класса:

Point pDown = Point.Empty;
Rectangle rect = Rectangle.Empty;

Три события мыши:

private void PictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    pDown = e.Location;
    pictureBox1.Refresh();
}

private void PictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    if (!e.Button.HasFlag(MouseButtons.Left)) return;

    rect = new Rectangle(pDown, new Size(e.X - pDown.X, e.Y - pDown.Y));
    using (Graphics g = pictureBox1.CreateGraphics())
    {
        pictureBox1.Refresh();
        g.DrawRectangle(Pens.Orange, rect);
    }
}

private void PictureBox1_MouseUp(object sender, MouseEventArgs e)
{
    Rectangle iR = ImageArea(pictureBox2);
    rect = new Rectangle(pDown.X - iR.X, pDown.Y - iR.Y, 
                         e.X - pDown.X, e.Y - pDown.Y);
    Rectangle rectSrc = Scaled(rect, pictureBox2, true);
    Rectangle rectDest = new Rectangle(Point.Empty, rectSrc.Size);

    Bitmap bmp = new Bitmap(rectDest.Width, rectDest.Height);
    using (Graphics g = Graphics.FromImage(bmp))
    {
        g.DrawImage(pictureBox2.Image, rectDest, rectSrc, GraphicsUnit.Pixel);
    }
    pictureBox2.Image = bmp;
}

Вот полезная функция, которая возвращает площадь фактического изображения внутри изображения для любого режима размера..:

Rectangle ImageArea(PictureBox pbox)
{
    Size si = pbox.Image.Size;
    Size sp = pbox.ClientSize;

    if (pbox.SizeMode == PictureBoxSizeMode.StretchImage) 
       return pbox.ClientRectangle;
    if (pbox.SizeMode == PictureBoxSizeMode.Normal ||
        pbox.SizeMode == PictureBoxSizeMode.AutoSize) 
       return new Rectangle(Point.Empty, si);
    if (pbox.SizeMode == PictureBoxSizeMode.CenterImage)
        return new Rectangle(new Point((sp.Width - si.Width) / 2,
                            (sp.Height - si.Height) / 2), si);

    //  PictureBoxSizeMode.Zoom
    float ri = 1f * si.Width / si.Height;
    float rp = 1f * sp.Width / sp.Height;
    if (rp > ri)
    {
        int width = si.Width * sp.Height / si.Height;
        int left = (sp.Width - width) / 2;
        return new Rectangle(left, 0, width, sp.Height);
    }
    else
    {
        int height = si.Height * sp.Width / si.Width;
        int top = (sp.Height - height) / 2;
        return new Rectangle(0, top, sp.Width, height);
    }
}

Нам нужно только смещение, чтобы определить немасштабированный прямоугольник. Нам также нужно масштабировать его:

Rectangle Scaled(Rectangle rect, PictureBox pbox, bool scale)
{
    float factor = GetFactor(pbox);
    if (!scale) factor = 1f / factor;
    return Rectangle.Round(new RectangleF(rect.X * factor, rect.Y * factor,  
                               rect.Width * factor, rect.Height * factor));
}

Для этого нужно знать коэффициент масштабирования, который зависит от соотношения сторон:

float GetFactor(PictureBox pBox)
{
    if (pBox.Image == null) return 0;
    Size si = pBox.Image.Size;
    Size sp = pBox.ClientSize;
    float ri = 1f * si.Width / si.Height;
    float rp = 1f * sp.Width / sp.Height;
    float factor = 1f * pBox.Image.Width / pBox.ClientSize.Width;
    if (rp > ri) factor = 1f * pBox.Image.Height / pBox.ClientSize.Height;
    return factor;
}

Это решение также будет работать, если PictureBox равно увеличено или уменьшено, поместив его внутрь AutoScrolling Panel и изменив Pbox.Size.

Другие вопросы по теме