C#中的大,奇比图像调整大小

时间:2017-04-27 12:03:34

标签: c# image graphics resize interpolation

我有一个特殊的问题,我需要帮助。我正在处理复杂的蛋白质组学数据,我们的一个图表涉及原始数据的热图。这些热图我计算为原始图像,然后我调整大小以适合我的图表画布。当涉及宽度与高度时,以这种方式生成的图像文件通常非常不平衡。 通常,这些图像的宽度大约为10到100像素,高度为5000到8000像素(这是我必须转换为图像的原始2D数据阵列的大小)。之后的目标分辨率将是1300 x 600像素。

我通常使用此功能将图像调整为目标尺寸

public static Image Resize(Image img, int width, int height) {
   Bitmap bmp = new Bitmap(width, height);
   Graphics graphic = Graphics.FromImage((Image)bmp);
   graphic.InterpolationMode = InterpolationMode.NearestNeighbor;
   graphic.PixelOffsetMode = PixelOffsetMode.Half;


   graphic.DrawImage(img, 0, 0, width, height);
   graphic.Dispose();

   return (Image)bmp;
}

这通常适用于上述尺寸。但现在我有了一个尺寸为6 x 54343像素的新数据集。 在此图像上使用相同的代码时,已调整大小的图像为半空白。

原始图片: http://files.biognosys.ch/FileSharing/20170427_StackOverflow/raw.png

(原始图像在大多数浏览器中无法正常显示,因此请使用“将链接另存为...”)

应该如何看待(使用photoshop): http://files.biognosys.ch/FileSharing/20170427_StackOverflow/photoshop_resize.png

当我使用上面剪切的代码时,它的外观如何 http://files.biognosys.ch/FileSharing/20170427_StackOverflow/code_resized.png

请记住,对于6 x 8000的图像,这已经工作多年没有问题所以我想我在这里没有做任何根本性的错误。 同样重要的是我有调整大小的NearestNeighbor插值,因此任何涉及其他插值的解决方案都不会导致“如何看”图像最终对我没用。

奥利

2 个答案:

答案 0 :(得分:6)

看起来你已经从16位Windows时代遇到了一些遗留限制。解决这个问题的一个显而易见的方法是使用内存操作将源映像预分割为较小的块,然后使用Graphics调整所有这些块的大小。此方法假定您的源图像为Bitmap而不仅仅是Image,但这似乎不是对您的限制。这是代码草图:

[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = true)]
public static extern void CopyMemoryUnmanaged(IntPtr dest, IntPtr src, int count);

// in case you can't use P/Invoke, copy via intermediate .Net buffer        
static void CopyMemoryNet(IntPtr dst, IntPtr src, int count)
{
    byte[] buffer = new byte[count];
    Marshal.Copy(src, buffer, 0, count);
    Marshal.Copy(buffer, 0, dst, count);
}

static Image CopyImagePart(Bitmap srcImg, int startH, int endH)
{
    var width = srcImg.Width;
    var height = endH - startH;
    var srcBitmapData = srcImg.LockBits(new Rectangle(0, startH, width, height), ImageLockMode.ReadOnly, srcImg.PixelFormat);

    var dstImg = new Bitmap(width, height, srcImg.PixelFormat);
    var dstBitmapData = dstImg.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, srcImg.PixelFormat);

    int bytesCount = Math.Abs(srcBitmapData.Stride) * height;
    CopyMemoryUnmanaged(dstBitmapData.Scan0, srcBitmapData.Scan0, bytesCount);
    // in case you can't use P/Invoke, copy via intermediate .Net buffer        
    //CopyMemoryNet(dstBitmapData.Scan0, srcBitmapData.Scan0, bytesCount);

    srcImg.UnlockBits(srcBitmapData);
    dstImg.UnlockBits(dstBitmapData);

    return dstImg;
}


public static Image ResizeInParts(Bitmap srcBmp, int width, int height)
{
    int srcStep = srcBmp.Height;
    int dstStep = height;
    while (srcStep > 30000)
    {
        srcStep /= 2;
        dstStep /= 2;
    }

    var resBmp = new Bitmap(width, height);
    using (Graphics graphic = Graphics.FromImage(resBmp))
    {
        graphic.InterpolationMode = InterpolationMode.NearestNeighbor;
        graphic.PixelOffsetMode = PixelOffsetMode.Half;


        for (int srcTop = 0, dstTop = 0; srcTop < srcBmp.Height; srcTop += srcStep, dstTop += dstStep)
        {
            int srcBottom = srcTop + srcStep;
            int dstH = dstStep;
            if (srcBottom > srcBmp.Height)
            {
                srcBottom = srcBmp.Height;
                dstH = height - dstTop;
            }
            using (var imgPart = CopyImagePart(srcBmp, srcTop, srcBottom))
            {
                graphic.DrawImage(imgPart, 0, dstTop, width, dstH);
            }
        }
    }

    return resBmp;
}

以下是我为您的示例图片获取的内容: resized image

与您的photoshop_resize.png不同,但与您的code_resized.png非常相似

可以改进此代码以更好地处理各种“边缘”,例如srcBmp.Height不均匀的情况或不同部分之间的边缘(边缘上的像素仅使用它们应该的一半像素进行插值)但是如果不假设源和调整大小的图像的某些“好”大小或者自己重新实现插值逻辑,这并不容易。考虑到缩放因子,这段代码可能已经足够好了。

答案 1 :(得分:2)

这是一个似乎有效的解决方案。它基于Windows WIC(&#34; Windows Imaging Component&#34;)。它是Windows(和WPF)用于所有成像操作的本机组件。

我为它提供了一个小的.NET互操作层。它没有所有WIC功能,但它允许您加载/缩放/保存文件/流图像。 Scale方法有一个类似于GDI +的缩放选项。

虽然结果并不完全等同于photoshop,但它似乎与您的样本一致。这就是你如何使用它:

using (var bmp = WicBitmapSource.Load("input.png"))
{
    bmp.Scale(1357, 584, WicBitmapInterpolationMode.NearestNeighbor);
    bmp.Save("output.png");
}

...

public enum WicBitmapInterpolationMode
{
    NearestNeighbor = 0,
    Linear = 1,
    Cubic = 2,
    Fant = 3,
    HighQualityCubic = 4,
}

public sealed class WicBitmapSource : IDisposable
{
    private IWICBitmapSource _source;

    private WicBitmapSource(IWICBitmapSource source, Guid format)
    {
        _source = source;
        Format = format;
        Stats();
    }

    public Guid Format { get; }
    public int Width { get; private set; }
    public int Height { get; private set; }
    public double DpiX { get; private set; }
    public double DpiY { get; private set; }

    private void Stats()
    {
        if (_source == null)
        {
            Width = 0;
            Height = 0;
            DpiX = 0;
            DpiY = 0;
            return;
        }

        int w, h;
        _source.GetSize(out w, out h);
        Width = w;
        Height = h;

        double dpix, dpiy;
        _source.GetResolution(out dpix, out dpiy);
        DpiX = dpix;
        DpiY = dpiy;
    }

    private void CheckDisposed()
    {
        if (_source == null)
            throw new ObjectDisposedException(null);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~WicBitmapSource()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (_source != null)
        {
            Marshal.ReleaseComObject(_source);
            _source = null;
        }
    }

    public void Save(string filePath)
    {
        Save(filePath, Format, Guid.Empty);
    }

    public void Save(string filePath, Guid pixelFormat)
    {
        Save(filePath, Format, pixelFormat);
    }

    public void Save(string filePath, Guid encoderFormat, Guid pixelFormat)
    {
        if (filePath == null)
            throw new ArgumentNullException(nameof(filePath));

        if (encoderFormat == Guid.Empty)
        {
            string ext = Path.GetExtension(filePath).ToLowerInvariant();
            // we support only png & jpg
            if (ext == ".png")
            {
                encoderFormat = new Guid(0x1b7cfaf4, 0x713f, 0x473c, 0xbb, 0xcd, 0x61, 0x37, 0x42, 0x5f, 0xae, 0xaf);
            }
            else if (ext == ".jpeg" || ext == ".jpe" || ext == ".jpg" || ext == ".jfif" || ext == ".exif")
            {
                encoderFormat = new Guid(0x19e4a5aa, 0x5662, 0x4fc5, 0xa0, 0xc0, 0x17, 0x58, 0x02, 0x8e, 0x10, 0x57);
            }
        }

        if (encoderFormat == Guid.Empty)
            throw new ArgumentException();

        using (var file = File.OpenWrite(filePath))
        {
            Save(file, encoderFormat, pixelFormat);
        }
    }

    public void Save(Stream stream)
    {
        Save(stream, Format, Guid.Empty);
    }

    public void Save(Stream stream, Guid pixelFormat)
    {
        Save(stream, Format, pixelFormat);
    }

    public void Save(Stream stream, Guid encoderFormat, Guid pixelFormat)
    {
        if (stream == null)
            throw new ArgumentNullException(nameof(stream));

        CheckDisposed();
        Save(_source, stream, encoderFormat, pixelFormat, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache, null);
    }

    public void Scale(int? width, int? height, WicBitmapInterpolationMode mode)
    {
        if (!width.HasValue && !height.HasValue)
            throw new ArgumentException();

        int neww;
        int newh;
        if (width.HasValue && height.HasValue)
        {
            neww = width.Value;
            newh = height.Value;
        }
        else
        {
            int w = Width;
            int h = Height;
            if (w == 0 || h == 0)
                return;

            if (width.HasValue)
            {
                neww = width.Value;
                newh = (width.Value * h) / w;
            }
            else
            {
                newh = height.Value;
                neww = (height.Value * w) / h;
            }
        }

        if (neww <= 0 || newh <= 0)
            throw new ArgumentException();

        CheckDisposed();
        _source = Scale(_source, neww, newh, mode);
        Stats();
    }

    // we support only 1-framed files (unlike TIF for example)
    public static WicBitmapSource Load(string filePath)
    {
        if (filePath == null)
            throw new ArgumentNullException(nameof(filePath));

        return LoadBitmapSource(filePath, 0, WICDecodeOptions.WICDecodeMetadataCacheOnDemand);
    }

    public static WicBitmapSource Load(Stream stream)
    {
        if (stream == null)
            throw new ArgumentNullException(nameof(stream));

        return LoadBitmapSource(stream, 0, WICDecodeOptions.WICDecodeMetadataCacheOnDemand);
    }

    private static WicBitmapSource LoadBitmapSource(string filePath, int frameIndex, WICDecodeOptions metadataOptions)
    {
        var wfac = (IWICImagingFactory)new WICImagingFactory();
        IWICBitmapDecoder decoder = null;
        try
        {
            decoder = wfac.CreateDecoderFromFilename(filePath, null, GenericAccessRights.GENERIC_READ, metadataOptions);
            return new WicBitmapSource(decoder.GetFrame(frameIndex), decoder.GetContainerFormat());
        }
        finally
        {
            Release(decoder);
            Release(wfac);
        }
    }

    private static WicBitmapSource LoadBitmapSource(Stream stream, int frameIndex, WICDecodeOptions metadataOptions)
    {
        var wfac = (IWICImagingFactory)new WICImagingFactory();
        IWICBitmapDecoder decoder = null;
        try
        {
            decoder = wfac.CreateDecoderFromStream(new ManagedIStream(stream), null, metadataOptions);
            return new WicBitmapSource(decoder.GetFrame(frameIndex), decoder.GetContainerFormat());
        }
        finally
        {
            Release(decoder);
            Release(wfac);
        }
    }

    private static IWICBitmapScaler Scale(IWICBitmapSource source, int width, int height, WicBitmapInterpolationMode mode)
    {
        var wfac = (IWICImagingFactory)new WICImagingFactory();
        IWICBitmapScaler scaler = null;
        try
        {
            scaler = wfac.CreateBitmapScaler();
            scaler.Initialize(source, width, height, mode);
            Marshal.ReleaseComObject(source);
            return scaler;
        }
        finally
        {
            Release(wfac);
        }
    }

    private static void Save(IWICBitmapSource source, Stream stream, Guid containerFormat, Guid pixelFormat, WICBitmapEncoderCacheOption cacheOptions, WICRect rect)
    {
        var wfac = (IWICImagingFactory)new WICImagingFactory();
        IWICBitmapEncoder encoder = null;
        IWICBitmapFrameEncode frame = null;
        try
        {
            encoder = wfac.CreateEncoder(containerFormat, null);
            encoder.Initialize(new ManagedIStream(stream), cacheOptions);
            encoder.CreateNewFrame(out frame, IntPtr.Zero);
            frame.Initialize(IntPtr.Zero);

            if (pixelFormat != Guid.Empty)
            {
                frame.SetPixelFormat(pixelFormat);
            }

            frame.WriteSource(source, rect);
            frame.Commit();
            encoder.Commit();
        }
        finally
        {
            Release(frame);
            Release(encoder);
            Release(wfac);
        }
    }

    private static void Release(object obj)
    {
        if (obj != null)
        {
            Marshal.ReleaseComObject(obj);
        }
    }

    [ComImport]
    [Guid("CACAF262-9370-4615-A13B-9F5539DA4C0A")]
    private class WICImagingFactory
    {
    }

    [StructLayout(LayoutKind.Sequential)]
    private class WICRect
    {
        public int X;
        public int Y;
        public int Width;
        public int Height;
    }

    [Flags]
    private enum WICDecodeOptions
    {
        WICDecodeMetadataCacheOnDemand = 0x0,
        WICDecodeMetadataCacheOnLoad = 0x1,
    }

    [Flags]
    private enum WICBitmapEncoderCacheOption
    {
        WICBitmapEncoderCacheInMemory = 0x0,
        WICBitmapEncoderCacheTempFile = 0x1,
        WICBitmapEncoderNoCache = 0x2,
    }

    [Flags]
    private enum GenericAccessRights : uint
    {
        GENERIC_READ = 0x80000000,
        GENERIC_WRITE = 0x40000000,
        GENERIC_EXECUTE = 0x20000000,
        GENERIC_ALL = 0x10000000,

        GENERIC_READ_WRITE = GENERIC_READ | GENERIC_WRITE
    }

    [Guid("ec5ec8a9-c395-4314-9c77-54d7a935ff70"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IWICImagingFactory
    {
        IWICBitmapDecoder CreateDecoderFromFilename([MarshalAs(UnmanagedType.LPWStr)] string wzFilename, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] Guid[] pguidVendor, GenericAccessRights dwDesiredAccess, WICDecodeOptions metadataOptions);
        IWICBitmapDecoder CreateDecoderFromStream(IStream pIStream, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] Guid[] pguidVendor, WICDecodeOptions metadataOptions);

        void NotImpl2();
        void NotImpl3();
        void NotImpl4();

        IWICBitmapEncoder CreateEncoder([MarshalAs(UnmanagedType.LPStruct)] Guid guidContainerFormat, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] Guid[] pguidVendor);

        void NotImpl6();
        void NotImpl7();

        IWICBitmapScaler CreateBitmapScaler();

        // not fully impl...
    }

    [Guid("00000120-a8f2-4877-ba0a-fd2b6645fb94"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IWICBitmapSource
    {
        void GetSize(out int puiWidth, out int puiHeight);
        Guid GetPixelFormat();
        void GetResolution(out double pDpiX, out double pDpiY);

        void NotImpl3();
        void NotImpl4();
    }

    [Guid("00000302-a8f2-4877-ba0a-fd2b6645fb94"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IWICBitmapScaler : IWICBitmapSource
    {
        #region IWICBitmapSource
        new void GetSize(out int puiWidth, out int puiHeight);
        new Guid GetPixelFormat();
        new void GetResolution(out double pDpiX, out double pDpiY);
        new void NotImpl3();
        new void NotImpl4();
        #endregion IWICBitmapSource

        void Initialize(IWICBitmapSource pISource, int uiWidth, int uiHeight, WicBitmapInterpolationMode mode);
    }

    [Guid("9EDDE9E7-8DEE-47ea-99DF-E6FAF2ED44BF"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IWICBitmapDecoder
    {
        void NotImpl0();
        void NotImpl1();

        Guid GetContainerFormat();

        void NotImpl3();
        void NotImpl4();
        void NotImpl5();
        void NotImpl6();
        void NotImpl7();
        void NotImpl8();
        void NotImpl9();

        IWICBitmapFrameDecode GetFrame(int index);
    }

    [Guid("3B16811B-6A43-4ec9-A813-3D930C13B940"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IWICBitmapFrameDecode : IWICBitmapSource
    {
        // not fully impl...
    }

    [Guid("00000103-a8f2-4877-ba0a-fd2b6645fb94"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IWICBitmapEncoder
    {
        void Initialize(IStream pIStream, WICBitmapEncoderCacheOption cacheOption);
        Guid GetContainerFormat();

        void NotImpl2();
        void NotImpl3();
        void NotImpl4();
        void NotImpl5();
        void NotImpl6();

        void CreateNewFrame(out IWICBitmapFrameEncode ppIFrameEncode, IntPtr encoderOptions);
        void Commit();

        // not fully impl...
    }

    [Guid("00000105-a8f2-4877-ba0a-fd2b6645fb94"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IWICBitmapFrameEncode
    {
        void Initialize(IntPtr pIEncoderOptions);
        void SetSize(int uiWidth, int uiHeight);
        void SetResolution(double dpiX, double dpiY);
        void SetPixelFormat([MarshalAs(UnmanagedType.LPStruct)] Guid pPixelFormat);

        void NotImpl4();
        void NotImpl5();
        void NotImpl6();
        void NotImpl7();

        void WriteSource(IWICBitmapSource pIBitmapSource, WICRect prc);
        void Commit();

        // not fully impl...
    }

    private class ManagedIStream : IStream
    {
        private Stream _stream;

        public ManagedIStream(Stream stream)
        {
            _stream = stream;
        }

        public void Read(byte[] buffer, int count, IntPtr pRead)
        {
            int read = _stream.Read(buffer, 0, count);
            if (pRead != IntPtr.Zero)
            {
                Marshal.WriteInt32(pRead, read);
            }
        }

        public void Seek(long offset, int origin, IntPtr newPosition)
        {
            long pos = _stream.Seek(offset, (SeekOrigin)origin);
            if (newPosition != IntPtr.Zero)
            {
                Marshal.WriteInt64(newPosition, pos);
            }
        }

        public void SetSize(long newSize)
        {
            _stream.SetLength(newSize);
        }

        public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG stg, int flags)
        {
            const int STGTY_STREAM = 2;
            stg = new System.Runtime.InteropServices.ComTypes.STATSTG();
            stg.type = STGTY_STREAM;
            stg.cbSize = _stream.Length;
            stg.grfMode = 0;

            if (_stream.CanRead && _stream.CanWrite)
            {
                const int STGM_READWRITE = 0x00000002;
                stg.grfMode |= STGM_READWRITE;
                return;
            }

            if (_stream.CanRead)
            {
                const int STGM_READ = 0x00000000;
                stg.grfMode |= STGM_READ;
                return;
            }

            if (_stream.CanWrite)
            {
                const int STGM_WRITE = 0x00000001;
                stg.grfMode |= STGM_WRITE;
                return;
            }

            throw new IOException();
        }

        public void Write(byte[] buffer, int count, IntPtr written)
        {
            _stream.Write(buffer, 0, count);
            if (written != IntPtr.Zero)
            {
                Marshal.WriteInt32(written, count);
            }
        }

        public void Clone(out IStream ppstm) { throw new NotImplementedException(); }
        public void Commit(int grfCommitFlags) { throw new NotImplementedException(); }
        public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) { throw new NotImplementedException(); }
        public void LockRegion(long libOffset, long cb, int dwLockType) { throw new NotImplementedException(); }
        public void Revert() { throw new NotImplementedException(); }
        public void UnlockRegion(long libOffset, long cb, int dwLockType) { throw new NotImplementedException(); }
    }
}