2017-04-27 91 views
7

我有一個特殊的問題,我需要幫助。我正在處理複雜的蛋白質組學數據,我們的一個地塊涉及到原始數據的熱圖。這些熱量圖我計算爲原始圖像,然後調整大小以適合我的圖表畫布。這種方式生成的圖像文件在寬度和高度方面通常非常平衡。通常,這些圖像大約有10到100像素寬和5000到8000像素高(這是我必須轉換成圖像的原始2D數據陣列的大小)。之後的目標分辨率將爲1300 x 600像素。C#中大,奇數比例的圖像大小調整

我通常使用此功能調整我的形象爲目標大小

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×8000的圖像,所以我想我沒有在這裏做任何根本性的錯誤。 對於調整大小我有NearestNeighbor插值也很重要,所以涉及其他插值的任何解決方案都不會導致「應該顯示的圖像」圖像最終對我無用。

Oli

+0

你可能會發現調整大小的其他設置有用。我自己對此不夠了解,但[this](http://stackoverflow.com/a/24199315/362432)似乎是一個徹底的答案... – Kempeth

+0

不,第一個鏈接沒有壞掉。但大多數瀏覽器都顯示圖像的問題,因爲它是6 x 54343. 嘗試「保存鏈接爲」 –

+0

哦,沒有。水晶球是錯誤的。 100%dpi設置。還有,再次。它在6 x 8000的情況下運行正常。但是,即使這會是問題,我仍然需要一個解決方案。我不能簡單地告訴我們的用戶他們必須更改DPI設置,否則他們不能使用該軟件。 –

回答

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

這個代碼可以被改進,以更好地處理各種「邊緣」,例如當不是偶數或不同部分之間的邊緣時(邊緣上的像素僅使用它們應該是的像素的一半來插入),但是這並不容易假設如此我的「好」尺寸的源和調整大小的圖像或重新實現插值邏輯自己。鑑於您的縮放因素,此代碼可能已經足夠用於您的使用。

+1

不是我希望的答案,但知道這是一個固有的16位時代問題,至少可以解釋它。我非常感謝你的代碼示例。當然,我寧願不必爲了更好地表現邊緣而做大塊部分,但是這比現在更好。非常感謝你。你值得加分。 你是否知道限制在哪裏?所以如果不是絕對必要的話,我不必做拆分操作。 –

+1

正如@HansPassant指出的那樣,真正的極限值應該在2^15左右,即32767或32768之間。在我的代碼中,我有30000作爲粗略估計(並且在安全方面)。此外,我不確定您接受的答案的獎勵是否不成熟。我建議你等待並查看其他答案。例如,SimonMourier的回答看起來很有前途(我沒有嘗試) – SergGr

+0

我想獎勵你們兩個人的賞金,但是因爲你是第一個給我提供可行解決方案的人,你應該得到它:) –

2

這是一個似乎可行的解決方案。它基於Windows WIC(「Windows Imaging Component」)。它是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(); } 
    } 
}