taji01
taji01

Reputation: 2615

How to resize image before it opens in picturebox

Overview
I'm using WinForms. In my code I have this method. I'm attempting to proportionally re-size tif images using this code . My goal is to get the image from the stream (source) and re-size it. The images I want to re-size is tif documents that has multiple pages. How can i re-size the image proportionally?

Code functionality & Plan
The code below is caching tif images in another thread. The problem is that I work with really big tif images. When I open these really big tif images with multiple pages my application crashes and lags. My goal or thought is before the images load i should re-size it so the application wont crash and the performance becomes faster.

Example Tif Document:

I created a large tif document for testing you can download it from this link: http://www.filedropper.com/tiftestingdoc

class PageBuffer : IDisposable
{
public const int DefaultCacheSize = 5;

public static PageBuffer Open(string path, int cacheSize = DefaultCacheSize)
{
    return new PageBuffer(File.OpenRead(path), cacheSize);
}

private PageBuffer(Stream stream, int cacheSize)
{
    this.stream = stream;
    source = Image.FromStream(stream);
    pageCount = source.GetFrameCount(FrameDimension.Page);
    if (pageCount < 2) return;
    pageCache = new Image[Math.Min(pageCount, Math.Max(cacheSize, 3))];
    var worker = new Thread(LoadPages) { IsBackground = true };
    worker.Start();
}

private void LoadPages()
{
    while (true)
    {
        lock (syncLock)
        {
            if (disposed) return;
            int index = Array.FindIndex(pageCache, 0, pageCacheSize, p => p == null);
            if (index < 0)
                Monitor.Wait(syncLock);
            else
                pageCache[index] = LoadPage(pageCacheStart + index);
        }
    }
}

private Image LoadPage(int index)
{
    source.SelectActiveFrame(FrameDimension.Page, index);
    return new Bitmap(source);
}

private Stream stream;
private Image source;
private int pageCount;
private Image[] pageCache;
private int pageCacheStart, pageCacheSize;
private object syncLock = new object();
private bool disposed;

public Image Source { get { return source; } }
public int PageCount { get { return pageCount; } }
public Image GetPage(int index)
{
    if (disposed) throw new ObjectDisposedException(GetType().Name);
    if (PageCount < 2) return Source;
    lock (syncLock)
    {
        AdjustPageCache(index);
        int cacheIndex = index - pageCacheStart;
        var image = pageCache[cacheIndex];
        if (image == null)
            image = pageCache[cacheIndex] = LoadPage(index);
        return image;
    }
}

private void AdjustPageCache(int pageIndex)
{
    int start, end;
    if ((start = pageIndex - pageCache.Length / 2) <= 0)
        end = (start = 0) + pageCache.Length;
    else if ((end = start + pageCache.Length) >= PageCount)
        start = (end = PageCount) - pageCache.Length;
    if (start < pageCacheStart)
    {
        int shift = pageCacheStart - start;
        if (shift >= pageCacheSize)
            ClearPageCache(0, pageCacheSize);
        else
        {
            ClearPageCache(pageCacheSize - shift, pageCacheSize);
            for (int j = pageCacheSize - 1, i = j - shift; i >= 0; j--, i--)
                Exchange(ref pageCache[i], ref pageCache[j]);
        }
    }
    else if (start > pageCacheStart)
    {
        int shift = start - pageCacheStart;
        if (shift >= pageCacheSize)
            ClearPageCache(0, pageCacheSize);
        else
        {
            ClearPageCache(0, shift);
            for (int j = 0, i = shift; i < pageCacheSize; j++, i++)
                Exchange(ref pageCache[i], ref pageCache[j]);
        }
    }
    if (pageCacheStart != start || pageCacheStart + pageCacheSize != end)
    {
        pageCacheStart = start;
        pageCacheSize = end - start;
        Monitor.Pulse(syncLock);
    }
}

void ClearPageCache(int start, int end)
{
    for (int i = start; i < end; i++)
        Dispose(ref pageCache[i]);
}

static void Dispose<T>(ref T target) where T : class, IDisposable
{
    var value = target;
    if (value != null) value.Dispose();
    target = null;
}

static void Exchange<T>(ref T a, ref T b) { var c = a; a = b; b = c; }

public void Dispose()
{
    if (disposed) return;
    lock (syncLock)
    {
        disposed = true;
        if (pageCache != null)
        {
            ClearPageCache(0, pageCacheSize);
            pageCache = null;
        }
        Dispose(ref source);
        Dispose(ref stream);
        if (pageCount > 2)
            Monitor.Pulse(syncLock);
    }
  }
}

Upvotes: 2

Views: 270

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205799

One attempt would be to limit the size of the cached bitmaps.

In order to do that, let introduce a new member

private Size pageSize;

and use it as follows

private Image LoadPage(int index)
{
    source.SelectActiveFrame(FrameDimension.Page, index);
    return new Bitmap(source, pageSize);
}

The last line does actual resizing according to Bitmap Constructor (Image, Size) documentation:

Initializes a new instance of the Bitmap class from the specified existing image, scaled to the specified size.

The we need to introduce additional parameter:

public static PageBuffer Open(string path, Size maxSize, int cacheSize = DefaultCacheSize)
{
    return new PageBuffer(File.OpenRead(path), maxSize, cacheSize);
}

private PageBuffer(Stream stream, Size maxSize, int cacheSize)
{
    // ...
} 

so you can pass the maximum desired size. The actual page size under that limit, keeping the original image size proportion is calculated like this:

var sourceSize = source.Size;
float scale = Math.Min((float)maxSize.Width / sourceSize.Width, (float)maxSize.Height / sourceSize.Height);
var targetSize = new Size((int)(sourceSize.Width * scale), (int)(sourceSize.Height * scale));

Here is the full code:

class PageBuffer : IDisposable
{
    public const int DefaultCacheSize = 5;

    public static PageBuffer Open(string path, Size maxSize, int cacheSize = DefaultCacheSize)
    {
        return new PageBuffer(File.OpenRead(path), maxSize, cacheSize);
    }

    private PageBuffer(Stream stream, Size maxSize, int cacheSize)
    {
        this.stream = stream;
        source = Image.FromStream(stream);
        pageCount = source.GetFrameCount(FrameDimension.Page);
        if (pageCount < 2) return;
        pageCache = new Image[Math.Min(pageCount, Math.Max(cacheSize, 3))];
        pageSize = source.Size;
        if (!maxSize.IsEmpty)
        {
            float scale = Math.Min((float)maxSize.Width / pageSize.Width, (float)maxSize.Height / pageSize.Height);
            pageSize = new Size((int)(pageSize.Width * scale), (int)(pageSize.Height * scale));
        }
        var worker = new Thread(LoadPages) { IsBackground = true };
        worker.Start();
    }

    private void LoadPages()
    {
        while (true)
        {
            lock (syncLock)
            {
                if (disposed) return;
                int index = Array.FindIndex(pageCache, 0, pageCacheSize, p => p == null);
                if (index < 0)
                    Monitor.Wait(syncLock);
                else
                    pageCache[index] = LoadPage(pageCacheStart + index);
            }
        }
    }

    private Image LoadPage(int index)
    {
        source.SelectActiveFrame(FrameDimension.Page, index);
        return new Bitmap(source, pageSize);
    }

    private Stream stream;
    private Image source;
    private int pageCount;
    private Image[] pageCache;
    private int pageCacheStart, pageCacheSize;
    private object syncLock = new object();
    private bool disposed;
    private Size pageSize;

    public Image Source { get { return source; } }
    public int PageCount { get { return pageCount; } }
    public Image GetPage(int index)
    {
        if (disposed) throw new ObjectDisposedException(GetType().Name);
        if (PageCount < 2) return Source;
        lock (syncLock)
        {
            AdjustPageCache(index);
            int cacheIndex = index - pageCacheStart;
            var image = pageCache[cacheIndex];
            if (image == null)
                image = pageCache[cacheIndex] = LoadPage(index);
            return image;
        }
    }

    private void AdjustPageCache(int pageIndex)
    {
        int start, end;
        if ((start = pageIndex - pageCache.Length / 2) <= 0)
            end = (start = 0) + pageCache.Length;
        else if ((end = start + pageCache.Length) >= PageCount)
            start = (end = PageCount) - pageCache.Length;
        if (start < pageCacheStart)
        {
            int shift = pageCacheStart - start;
            if (shift >= pageCacheSize)
                ClearPageCache(0, pageCacheSize);
            else
            {
                ClearPageCache(pageCacheSize - shift, pageCacheSize);
                for (int j = pageCacheSize - 1, i = j - shift; i >= 0; j--, i--)
                    Exchange(ref pageCache[i], ref pageCache[j]);
            }
        }
        else if (start > pageCacheStart)
        {
            int shift = start - pageCacheStart;
            if (shift >= pageCacheSize)
                ClearPageCache(0, pageCacheSize);
            else
            {
                ClearPageCache(0, shift);
                for (int j = 0, i = shift; i < pageCacheSize; j++, i++)
                    Exchange(ref pageCache[i], ref pageCache[j]);
            }
        }
        if (pageCacheStart != start || pageCacheStart + pageCacheSize != end)
        {
            pageCacheStart = start;
            pageCacheSize = end - start;
            Monitor.Pulse(syncLock);
        }
    }

    void ClearPageCache(int start, int end)
    {
        for (int i = start; i < end; i++)
            Dispose(ref pageCache[i]);
    }

    static void Dispose<T>(ref T target) where T : class, IDisposable
    {
        var value = target;
        if (value != null) value.Dispose();
        target = null;
    }

    static void Exchange<T>(ref T a, ref T b) { var c = a; a = b; b = c; }

    public void Dispose()
    {
        if (disposed) return;
        lock (syncLock)
        {
            disposed = true;
            if (pageCache != null)
            {
                ClearPageCache(0, pageCacheSize);
                pageCache = null;
            }
            Dispose(ref source);
            Dispose(ref stream);
            if (pageCount > 2)
                Monitor.Pulse(syncLock);
        }
    }
}

Sample usage:

var data = PageBuffer.Open(path, new Size(850, 1100));

Upvotes: 1

Ashkan Mobayen Khiabani
Ashkan Mobayen Khiabani

Reputation: 34180

        Bitmap bmp = new Bitmap(newWidth, newHeight);
        Graphics g = Graphics.FromImage(bmp);
    for (int idx = 0; idx < count; idx++)
        {
            bmp.SelectActiveFrame(FrameDimension.Page, idx);
        g.DrawImage(source, new Rectangle(0,0,source.Width,source.Height), 
         new Rectangle(0,0,bmp.Width,bmp.Height));
            ImageCodecInfo myImageCodecInfo;
            System.Drawing.Imaging.Encoder myEncoder;
            EncoderParameter myEncoderParameter;
            EncoderParameters myEncoderParameters;
            myImageCodecInfo = GetEncoderInfo("image/tiff");
            myEncoder = System.Drawing.Imaging.Encoder.Compression;
            myEncoderParameters = new EncoderParameters(1);
            myEncoderParameter = new EncoderParameter(
                myEncoder,
                (long)EncoderValue.CompressionLZW);
            myEncoderParameters.Param[0] = myEncoderParameter;    
            bmp.SaveAdd(byteStream, ImageFormat.Tiff);
        }


 private static ImageCodecInfo GetEncoderInfo(String mimeType)
        {
            int j;
            ImageCodecInfo[] encoders;
            encoders = ImageCodecInfo.GetImageEncoders();
            for (j = 0; j < encoders.Length; ++j)
            {
                if (encoders[j].MimeType == mimeType)
                    return encoders[j];
            }
            return null;
        }

Upvotes: 0

Related Questions