2011-01-27 15 views
11

मान लीजिए कि मेरे पास 32bpp ARGB मोड में System.Drawing.Bitmap है। यह एक बड़ा बिटमैप है, लेकिन यह बीच में कहीं अपेक्षाकृत छोटी छवि के साथ ज्यादातर पारदर्शी पिक्सल है।स्वचालित रूप से बिटमैप को न्यूनतम आकार में ट्रिम करें?

एक तेजी से 'असली' छवि की सीमाओं का पता लगाने के एल्गोरिथ्म क्या है, तो मैं इसके चारों ओर से सभी पारदर्शी पिक्सेल दूर क्रॉप कर सकते हैं?

वैकल्पिक रूप से, पहले से ही नेट में एक समारोह है कि मैं इस के लिए उपयोग कर सकते हैं?

+2

कटऑफ सीधे है? यदि हां, तो एल-> आर और टी-> बी से पिक्सल पढ़ना बहुत तेज़ काम करेगा। –

+0

यदि यह वर्ग है, तो आप संभवत: अधिक समय और बाइनरी को सभी 4 पक्षों पर केंद्र से बाहर खोज सकते हैं (कम से कम पिक्सेल पूछताछ पर काट रहे हैं) –

+0

क्या छोटी, एम्बेडेड छवि में इसके भीतर पारदर्शी पिक्सेल भी हो सकते हैं? –

उत्तर

23

मूल विचार शीर्ष, छोड़ दिया छवि के दाईं और नीचे सीमा को खोजने के लिए छवि के हर पिक्सेल की जांच करने के लिए है। इसे कुशलता से करने के लिए, GetPixel विधि का उपयोग न करें, जो कि बहुत धीमी है। इसके बजाय LockBits का उपयोग करें।

यहाँ कार्यान्वयन मैं के साथ आया है:

static Bitmap TrimBitmap(Bitmap source) 
{ 
    Rectangle srcRect = default(Rectangle); 
    BitmapData data = null; 
    try 
    { 
     data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 
     byte[] buffer = new byte[data.Height * data.Stride]; 
     Marshal.Copy(data.Scan0, buffer, 0, buffer.Length); 
     int xMin = int.MaxValue; 
     int xMax = 0; 
     int yMin = int.MaxValue; 
     int yMax = 0; 
     for (int y = 0; y < data.Height; y++) 
     { 
      for (int x = 0; x < data.Width; x++) 
      { 
       byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
       if (alpha != 0) 
       { 
        if (x < xMin) xMin = x; 
        if (x > xMax) xMax = x; 
        if (y < yMin) yMin = y; 
        if (y > yMax) yMax = y; 
       } 
      } 
     } 
     if (xMax < xMin || yMax < yMin) 
     { 
      // Image is empty... 
      return null; 
     } 
     srcRect = Rectangle.FromLTRB(xMin, yMin, xMax, yMax); 
    } 
    finally 
    { 
     if (data != null) 
      source.UnlockBits(data); 
    } 

    Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height); 
    Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height); 
    using (Graphics graphics = Graphics.FromImage(dest)) 
    { 
     graphics.DrawImage(source, destRect, srcRect, GraphicsUnit.Pixel); 
    } 
    return dest; 
} 

यह शायद अनुकूलित किया जा सकता है, लेकिन मैं एक GDI + विशेषज्ञ नहीं हूँ, तो यह सबसे अच्छा मैं आगे अनुसंधान के बिना कर सकते हैं ...


संपादित करें: righ के लिए छोड़ दिया

  1. स्कैन: वास्तव में, वहाँ यह अनुकूलन करने के लिए, छवि के कुछ भागों को स्कैन नहीं द्वारा एक आसान तरीका है टी जब तक आपको एक गैर पारदर्शी पिक्सेल नहीं मिल जाता है; स्टोर (एक्स, वाई) में (xMin, yMin)
  2. जब तक आप एक गैर पारदर्शी पिक्सेल (केवल x> = xMin के लिए) नहीं पाते हैं तब तक ऊपर तक स्कैन करें; y yin
  3. में स्टोर वाई को तब तक बाएं स्कैन करें जब तक कि आपको एक गैर-पारदर्शी पिक्सेल न मिले (केवल y> = ymin के लिए); एक्सएमएक्स
  4. स्टोर में x को एक गैर पारदर्शी पिक्सेल (केवल xMin < = x < = xMax) के लिए शीर्ष तक स्कैन करें; yMax

EDIT2 में दुकान y: यहाँ ऊपर दृष्टिकोण के एक कार्यान्वयन है:

static Bitmap TrimBitmap(Bitmap source) 
{ 
    Rectangle srcRect = default(Rectangle); 
    BitmapData data = null; 
    try 
    { 
     data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 
     byte[] buffer = new byte[data.Height * data.Stride]; 
     Marshal.Copy(data.Scan0, buffer, 0, buffer.Length); 

     int xMin = int.MaxValue, 
      xMax = int.MinValue, 
      yMin = int.MaxValue, 
      yMax = int.MinValue; 

     bool foundPixel = false; 

     // Find xMin 
     for (int x = 0; x < data.Width; x++) 
     { 
      bool stop = false; 
      for (int y = 0; y < data.Height; y++) 
      { 
       byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
       if (alpha != 0) 
       { 
        xMin = x; 
        stop = true; 
        foundPixel = true; 
        break; 
       } 
      } 
      if (stop) 
       break; 
     } 

     // Image is empty... 
     if (!foundPixel) 
      return null; 

     // Find yMin 
     for (int y = 0; y < data.Height; y++) 
     { 
      bool stop = false; 
      for (int x = xMin; x < data.Width; x++) 
      { 
       byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
       if (alpha != 0) 
       { 
        yMin = y; 
        stop = true; 
        break; 
       } 
      } 
      if (stop) 
       break; 
     } 

     // Find xMax 
     for (int x = data.Width - 1; x >= xMin; x--) 
     { 
      bool stop = false; 
      for (int y = yMin; y < data.Height; y++) 
      { 
       byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
       if (alpha != 0) 
       { 
        xMax = x; 
        stop = true; 
        break; 
       } 
      } 
      if (stop) 
       break; 
     } 

     // Find yMax 
     for (int y = data.Height - 1; y >= yMin; y--) 
     { 
      bool stop = false; 
      for (int x = xMin; x <= xMax; x++) 
      { 
       byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
       if (alpha != 0) 
       { 
        yMax = y; 
        stop = true; 
        break; 
       } 
      } 
      if (stop) 
       break; 
     } 

     srcRect = Rectangle.FromLTRB(xMin, yMin, xMax, yMax); 
    } 
    finally 
    { 
     if (data != null) 
      source.UnlockBits(data); 
    } 

    Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height); 
    Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height); 
    using (Graphics graphics = Graphics.FromImage(dest)) 
    { 
     graphics.DrawImage(source, destRect, srcRect, GraphicsUnit.Pixel); 
    } 
    return dest; 
} 

एक महत्वपूर्ण लाभ वहाँ नहीं होगा अगर अपारदर्शी हिस्सा निश्चित रूप से छोटा है, के बाद से यह अभी भी अधिकांश पिक्सेल स्कैन करेगा। लेकिन यदि यह बड़ा है, तो गैर पारदर्शी भाग के आसपास केवल आयताकार स्कैन किए जाएंगे।

+0

से बेहतर करने का कोई तरीका नहीं दिखता है, यह सबसे व्यावहारिक दृष्टिकोण की तरह लगता है। मुझे यह इससे बेहतर नहीं लगता है। लॉकबिट विधि के साथ भी अच्छी टिप। +1 –

+2

बीटीडब्ल्यू, मुझे एहसास हुआ कि ग्राफिक्स का उपयोग किए बिना छवि को फसल करने का एक आसान तरीका है: 'वापसी स्रोत। क्लोन (srcRect, source.PixelFormat);' –

+2

ग्रेट समाधान, बहुत उपयोगी, लेकिन मैंने पाया कि मेरी छवियां थीं एक पिक्सल से बहुत अधिक हो रही है। तार्किक रूप से आपका सही लगता है, लेकिन मैंने कॉल को ** आयत में बदल दिया। FROMLTRB ** से ** srcRect = Rectangle.FromLTRB (xMin, yMin, xMax + 1, yMax + 1) ** और अब यह पूरी तरह से काम करता है। –

1

मैं एक विभाजन & जीत दृष्टिकोण का सुझाव चाहते हैं:

  1. बीच में छवि को विभाजित (जैसेखड़ी)
  2. जांच (कट लाइन पर अपारदर्शी पिक्सेल देखते हैं अगर यदि हां, तो बॉक्स)
  3. विभाजन छोड़ दिया आधा बाउंडिंग के लिए याद न्यूनतम/अधिकतम फिर से खड़ी
  4. अगर कटौती लाइन अपारदर्शी पिक्सेल होता है -> अद्यतन बाउंडिंग बॉक्स
  5. यदि नहीं, तो संभवतः आप बाईं ओर आधे (मुझे चित्रों को नहीं जानते)
  6. बाएं-दाएं आधे के साथ जारी रखें (आपने कहा है कि छवि मध्य में कहीं है) जब तक आप पाते हैं छवि
  7. दाएं आधे
  8. के लिए समान करें
+3

मुझे लगता है कि आपका 5 वां बिंदु गलत है: गैर-पारदर्शी पिक्सल वाले कई अलग-अलग क्षेत्र हो सकते हैं, इसलिए तथ्य यह है कि कट लाइन पर कोई गैर-पारदर्शी पिक्सेल नहीं है, इसका मतलब यह नहीं है कि –

+0

धन्यवाद bjoernz, लेकिन हाँ: बाइनरी खोज हमेशा मेरी छवियों के लिए काम नहीं करते - उदाहरण के लिए, संभव है कि दो छवियों को व्हाइटस्पेस से अलग किया जा सके। – Blorgbeard

संबंधित मुद्दे