Cyotek.Windows.Forms.ImageBox icon indicating copy to clipboard operation
Cyotek.Windows.Forms.ImageBox copied to clipboard

Solution to: Exception 'Out of Memory' while drawing a big image

Open carlshe opened this issue 5 years ago • 2 comments

While load a big image, for example 15000*8000 pixels, DrawImage almost always throw an exception 'Out of Memeory'.

I tested a solution, which works very well. Its key point is to divide image into several small parts and then draw each part one by one.

The code as follow:

            int CountOfStepX = (int)Math.Round(srcPortion.Width / MaxDrawImageSidePixels + 0.5);
            int CountOfStepY = (int)Math.Round(srcPortion.Height / MaxDrawImageSidePixels + 0.5);

            float stepDstX = (int)(dstView.Width / CountOfStepX);
            float stepDstY = (int)(dstView.Height / CountOfStepY);
            float stepSrcX = (int)(srcPortion.Width / CountOfStepX);
            float stepSrcY = (int)(srcPortion.Height / CountOfStepY);
            for(int w=0;w< CountOfStepX;w++)
                for(int h=0; h<CountOfStepY;h++)
                {
                    rfDst = new RectangleF(dstView.X+ stepDstX*w, dstView.Y + stepDstY * h, stepDstX, stepDstY);
                    rfSrc=  new RectangleF(srcPortion.X+ stepSrcX *w, srcPortion.Y + stepSrcY * h, stepSrcX, stepSrcY);
                    Rectangle txtRect = new Rectangle((int)rfDst.X, (int)rfDst.Y, (int)rfDst.Width, (int)rfDst.Height);
                    g.DrawImage(this.Image, rfDst, rfSrc, GraphicsUnit.Pixel);
                }

carlshe avatar Feb 22 '20 17:02 carlshe

Good grief - this never crossed my mind. Curiously enough I had a support ticket recently for another user who was having difficulties with the ImageBox and giant images. I'd made a "rainy day note" after that to try and do a demo where multiple separate images are drawn as though they were a single image, similar to how things like online maps work. It just didn't twig that I could have done the same thing as you suggest.

Thanks very much for the tip - I will give that a go!

cyotek avatar Feb 22 '20 18:02 cyotek

I write a control derived from ImageBox. I would like to share the code here, and hope it could be useful. And hope the new version could be improved. Thanks!

public class PictureBox : ImageBox { public const float MaxDrawImageSidePixels = 5000;

    protected override void DrawImage(Graphics g)
    {
        Rectangle dstView = this.GetImageViewPort();
        RectangleF srcPortion = this.GetSourceImageRegion();
        if (srcPortion.Width <= MaxDrawImageSidePixels && srcPortion.Height <= MaxDrawImageSidePixels)
        {
            base.DrawImage(g);
            //TextRenderer.DrawText(g, srcPortion.ToString(), this.Font, this.ClientRectangle, this.ForeColor, this.BackColor, TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.WordBreak | TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix);
            return;
        }

        InterpolationMode currentInterpolationMode;
        PixelOffsetMode currentPixelOffsetMode;

        currentInterpolationMode = g.InterpolationMode;
        currentPixelOffsetMode = g.PixelOffsetMode;

        g.InterpolationMode = this.GetInterpolationMode();

        // disable pixel offsets. Thanks to Rotem for the info.
        // http://stackoverflow.com/questions/14070311/why-is-graphics-drawimage-cropping-part-of-my-image/14070372#14070372
        g.PixelOffsetMode = PixelOffsetMode.HighQuality;

        RectangleF rfDst=new RectangleF(0,0,100,100);
        RectangleF rfSrc = new RectangleF(0, 0, 100, 100);
        try
        {
            // Animation. Thanks to teamalpha5441 for the contribution
            if (this.IsAnimating && !this.DesignMode)
            {
                ImageAnimator.UpdateFrames(this.Image);
            }
            int CountOfStepX = (int)Math.Round(srcPortion.Width / MaxDrawImageSidePixels + 0.5);
            int CountOfStepY = (int)Math.Round(srcPortion.Height / MaxDrawImageSidePixels + 0.5);

            float stepDstX = (int)(dstView.Width / CountOfStepX);
            float stepDstY = (int)(dstView.Height / CountOfStepY);
            float stepSrcX = (int)(srcPortion.Width / CountOfStepX);
            float stepSrcY = (int)(srcPortion.Height / CountOfStepY);
            for(int w=0;w< CountOfStepX;w++)
                for(int h=0; h<CountOfStepY;h++)
                {
                    rfDst = new RectangleF(dstView.X+ stepDstX*w, dstView.Y + stepDstY * h, stepDstX, stepDstY);
                    rfSrc=  new RectangleF(srcPortion.X+ stepSrcX *w, srcPortion.Y + stepSrcY * h, stepSrcX, stepSrcY);
                    g.DrawImage(this.Image, rfDst, rfSrc, GraphicsUnit.Pixel);

                    //Rectangle txtRect = new Rectangle((int)rfDst.X, (int)rfDst.Y, (int)rfDst.Width, (int)rfDst.Height);
                    //TextRenderer.DrawText(g, rfDst.ToString() + " " + rfSrc.ToString(), this.Font, txtRect, this.ForeColor, this.BackColor, TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.WordBreak | TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix);
                }
        }
        catch (Exception ex)
        {
            TextRenderer.DrawText(g, ex.Message, this.Font, this.ClientRectangle, this.ForeColor, this.BackColor, TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.WordBreak | TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix);
        }

        g.PixelOffsetMode = currentPixelOffsetMode;
        g.InterpolationMode = currentInterpolationMode;
    }
}

carlshe avatar May 22 '22 08:05 carlshe