Подтвердить что ты не робот

Просмотр больших многостраничных изображений Tif в течение 100 миллисекунд

Я использую WinForms. В моей форме у меня есть pictureBox (установлен на normal mode), следующую и предыдущую кнопку. Я хочу быстро изменить размер и загрузить многостраничные изображения TIF. Когда я перехожу на следующую страницу в многостраничном TIF-изображении, я испытываю задержку каждый раз, когда изображение рисуется на pictureBox. Средняя скорость изображения составляет около 800 миллисекунд. Я хочу, чтобы страницы загружались в течение 100 миллисекунд.

Я хочу, чтобы производительность обработки больших изображений TIF выполнялась так же быстро, как IrfanView. IrfanView - небольшое приложение для просмотра изображений. Если вы загружаете IrfanView, вы можете видеть, насколько быстро это работает. В настоящее время у меня есть другое решение, в котором я использую многопоточную работу фона для загрузки страниц TIF в массив, а затем я масштабирую его. Этот метод требует некоторого времени изначально, но цель здесь не должна ждать.

Есть ли способ улучшить производительность Graphics.DrawImage для больших изображений в .NET?

g.DrawImage(img, 0, 0, ширина, высота);//Эта строка вызывает задержку "800 миллисекунд в зависимости от вашего компьютера"

  • Размер изображений TIF, с которыми я работаю: Width = 16800, Height = 10800
  • Только черно-белые изображения Tif
  • Глубина бит = 1
  • Единица разрешения = 2

введите описание изображения здесь

 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Data;
 using System.Diagnostics;
 using System.Drawing;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using System.Windows.Forms;

namespace Tif_Preformance_Question
{
public partial class Form1 : Form
{

    int counter = -1;
    int frameCount = 0;
    Stopwatch s = new Stopwatch();
    Image img;
    Image[] images;

    public Form1()
    {
        InitializeComponent();
    }

    private void btn_Open_Click(object sender, EventArgs e)
    {
        var s = new Stopwatch();
        s.Start();
        s.Stop();
        this.Text = "Elapsed Time Milliseconds" + s.ElapsedMilliseconds;


        img = Image.FromFile(@"C:\image\Large_Tif_Image_15pages.tif");
        frameCount = img.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page);
        images = new Image[frameCount];

        for (int i = 0; i < frameCount; i++)
        {
            img.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
            images[i] = (Image)img.Clone();
        }
        img.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, 0);
        pictureBox1.Image = (Image)img.Clone();

    }

    private void btn_Next_Click(object sender, EventArgs e)
    {
        counter++;
        if (counter >= frameCount)
        {
            counter = frameCount - 1;
            btn_Next.Enabled = false;
        }
        btn_Next.Enabled = false;
        LoadPage();
        btn_Next.Enabled = true;
    }

    private void LoadPage()
    {

        StartWatch();
        img.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, counter);
        pictureBox1.Image = ResizeImage((Image)img.Clone(), pictureBox1.Width, pictureBox1.Height);
        pictureBox1.Refresh();
        Stopwatch();
    }

    public Image ResizeImage(Image img, int width, int height)
    {
        Bitmap b = new Bitmap(width, height);
        using (Graphics g = Graphics.FromImage((Image)b))
        {
            g.DrawImage(img, 0, 0, width, height);
        }
        return (Image)b;
    }

    private void StartWatch()
    {
        s.Start();
    }
    private void Stopwatch()
    {

        s.Stop();
        this.Text = "Elapsed Time Milliseconds: " + s.ElapsedMilliseconds;
        s.Reset();
    }
  }
}

Ссылки

IrfanView:

http://www.irfanview.com/

Тест: изображение с большим TIF ниже

http://www.filedropper.com/largetifimage15pages_2

Решение Visual Studio

http://www.filedropper.com/tifpreformancequestion_1

4b9b3361

Ответ 1

Что очень дорого - это изменение размера изображения, потому что это большое изображение (у вас также есть дополнительный клон перед изменением размера, который кажется бесполезным и стоит около ~ 10%).

Я не уверен, что вы можете найти более быстрый загрузчик /resizer, возможно, irfan view написал один специально (TIF, как тот, что в вашем примере - это просто 1 Bpp B & W-образ. После загрузки изображения вы можете изменить размер многопоточный режим, нерестится, скажем, 2,4,8 или 16 рабочих потоков, каждый из которых находится на прямоугольной части изображения и делится в целом на количество потоков).

Без какой-либо третьей стороны, вот чистый .NET образец, который работает в вашей среде, с конкретным многопоточным SizeTifImage, который кэширует все фреймы, уже измененные в памяти. Когда вы запустите его, вы увидите только начальное время загрузки ~ 1 с, а затем просмотр изображений не должен быть заметным:

public partial class Form1 : Form
{
    SizedTifImage _tif;

    private void btn_Open_Click(object sender, EventArgs e)
    {
       ...
        _tif = new SizedTifImage(@"Large_Tif_Image_15pages.tif", pictureBox1.Width, pictureBox1.Height);
        pictureBox1.Image = _tif.GetFrame(0);
        btn_Next_Click(null, null);
    }

    private void btn_Next_Click(object sender, EventArgs e)
    {
        counter++;
        if (counter >= _tif.FrameCount)
        {
            counter = _tif.FrameCount - 1;
            btn_Next.Enabled = false;
        }
        btn_Next.Enabled = false;
        LoadPage();
        btn_Next.Enabled = true;
    }

    private void LoadPage()
    {
        StartWatch();
        pictureBox1.Image = _tif.GetFrame(counter);
        Stopwatch();
    }
}

public class SizedTifImage : IDisposable
{
    private Image _image;
    private ConcurrentDictionary<int, Image> _frames = new ConcurrentDictionary<int, Image>();

    public SizedTifImage(string filename, int width, int height)
    {
        Width = width;
        Height = height;
        _image = Image.FromFile(filename);
        FrameCount = _image.GetFrameCount(FrameDimension.Page);
        ThreadPool.QueueUserWorkItem(ResizeFrame);
    }

    public int FrameCount { get; private set; }
    public int Width { get; private set; }
    public int Height { get; private set; }

    private void ResizeFrame(object state)
    {
        for (int i = 0; i < FrameCount; i++)
        {
            if (_image == null)
                return;

            _image.SelectActiveFrame(FrameDimension.Page, i);
            var bmp = new Bitmap(Width, Height);
            using (var g = Graphics.FromImage(bmp))
            {
                if (_image == null)
                    return;

                g.DrawImage(_image, 0, 0, bmp.Width, bmp.Height);
            }
            _frames.AddOrUpdate(i, bmp, (k, oldValue) => { bmp.Dispose(); return oldValue; });
        }
    }

    public Image GetFrame(int i)
    {
        if (i >= FrameCount)
            throw new IndexOutOfRangeException();

        if (_image == null)
            throw new ObjectDisposedException("Image");

        Image img;
        do
        {
            if (_frames.TryGetValue(i, out img))
                return img;

            Thread.Sleep(10);
        }
        while (true);
    }

    public void Dispose()
    {
        var images = _frames.Values.ToArray();
        _frames.Clear();
        foreach (var img in images)
        {
            img.Dispose();
        }

        if (_image != null)
        {
            _image.Dispose();
            _image = null;
        }
    }

Ответ 2

Я подозреваю, что здесь есть несколько вопросов. Сначала я подозреваю, что IrfanView не написан на С#. С# - замечательный язык, но некоторые из его сильных сторон не всегда способствуют максимальной производительности. Например, С# имеет больше накладных расходов при работе с памятью (он очищает его при распределении, отслеживает использование и сбор мусора и т.д.).

Области, на которые я смотрю, - это ввода-вывода и потоки. На моей машине требуется ~ 30 мс, чтобы прочитать файл (это почти 1/3 вашего бюджета в 100 мс. Я подозреваю, что проблема с DrawImage заключается в том, что она не является потоковой (моя догадка). Для изменения размера она должна запустите 22 МБ данных на небайтовой границе, 10x10 1-битные пиксели в старом изображении должны быть обработаны, чтобы получить 1 пиксель в новом (масштабированном) изображении. Это можно проверить, просмотрев график CPU диспетчера задач (логические процессоры ) во время выполнения, а затем во время выполнения IrfanView.

Фиксирование любой проблемы может быть нетривиальным. Вы можете ускорить ввод/вывод с помощью ввода-вывода с памятью. Я подозреваю, что реальная победа будет нарезать размер; 800 мс /8 ядер ~ = 100 мс (поскольку изменение размеров очень параллелизуемо). Вы можете написать свой собственный потоковый ресивер, или может быть доступна сторонняя библиотека, доступная для того, чтобы делать то, что вам нужно. Или вы можете модифицировать библиотеку с открытым исходным кодом для потоковой передачи/ускорения.

Вы также можете посмотреть источник MS для вызова DrawImage здесь Он, как представляется, завершает вызов gdplus.dll GdipDrawImageRectI.

Вы также можете взглянуть на Параллелирование GDI + Изменение размера .net для идей

Ответ 3

У вас может быть преимущество в создании собственного PictureBox, который наследуется от оригинала. Вы можете переопределить OnPaint и настроить следующие параметры переданного объекта Graphics:

private override OnPaint(object sender, PaintEventArgs e)
{
    e.Graphics.CompositingQuality = CompositingQuality.HighSpeed;
    e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
    e.Graphics.SmoothingMode = SmoothingMode.None;
    e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
    e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
    e.Graphics.CompositingMode = CompositingMode.SourceCopy;
    base OnPaint(sender, e)
}

Некоторые из этих параметров оказывают огромное влияние на скорость рендеринга (и на качество результата).

Параметры, используемые в примере кода, уже довольно быстро, но, возможно, вы найдете лучшие комбинации для своих требований.

Ответ 4

Ваше изображение слишком велико. Изменение размера, которое обычно включает расчет сглаживания, может быть очень медленным.

Следовательно, единственный способ - использовать указатель для доступа к битам изображения и выборочного отображения пикселей.

public unsafe Image ResizeImage(Bitmap img, int width, int height)
{
    var stopwatch = Stopwatch.StartNew();

    var imgBits = img.LockBits(new Rectangle(Point.Empty, img.Size), ImageLockMode.ReadOnly, img.PixelFormat);

    Bitmap b = new Bitmap(width, height);
    var bBits = b.LockBits(new Rectangle(Point.Empty, b.Size), ImageLockMode.WriteOnly, b.PixelFormat);

    for (int j = 0; j < height; j++)
    {
        var imgJ = j * img.Height / height;

        for (int i = 0; i < width; i++)
        {
            var imgI = i * img.Width / width;

            var imgPointer = (byte*)imgBits.Scan0 + imgJ * imgBits.Stride + (imgI >> 3);
            var mask = (byte)(0x80 >> (imgI & 0x7));
            var imgPixel = (uint)(*imgPointer & mask);

            var bPointer = (uint*)bBits.Scan0 + j * bBits.Width + i;
            *bPointer = imgPixel > 0 ? 0x00FFFFFF : 0xFF000000;
        }
    }

    img.UnlockBits(imgBits);
    b.UnlockBits(bBits);

    stopwatch.Stop();
    Console.WriteLine("Resize to " + width + " x " + height + " within " + stopwatch.ElapsedMilliseconds + "ms");

    return b;
}

public void Test()
{
    var rawImage = new Bitmap(@"Large_Tif_Image_15pages.tif");
    rawImage.SelectActiveFrame(FrameDimension.Page, 3);

    pictureBox1.Image = ResizeImage(rawImage, pictureBox1.Width, pictureBox1.Height);
}

Измените размер до 525 x 345 в течение 31 мс

Результат значителен. Однако качество, конечно, не так хорошо, как 1 полный второй расчет.

var outputFactor = 1.5;
var outputWidth = (int)(pictureBox1.Width * outputFactor);
var outputHeight = (int)(pictureBox1.Height * outputFactor);
var outputImage = ResizeImage(rawImage, outputWidth, outputHeight);

Чтобы восстановить качество, измените размер с коэффициентом 1,5, указав более подробную информацию.

Баланс между скоростью и качеством.