2011-01-27 19 views
11

Supongamos que tengo un System.Drawing.Bitmap en el modo 32bpp ARGB. Es un mapa de bits grande, pero es en su mayoría píxeles totalmente transparentes con una imagen relativamente pequeña en algún lugar en el medio.¿Recortar automáticamente un mapa de bits al tamaño mínimo?

¿Qué es un algoritmo rápido para detectar los bordes de la imagen "real", por lo que puedo recortar todos los píxeles transparentes a su alrededor?

Alternativamente, ¿hay alguna función ya en .Net que pueda usar para esto?

+2

¿Está derecho el corte? de ser así, leer píxeles de L-> R y T-> B funcionaría muy rápido. –

+0

Si es cuadrado, probablemente puedas ahorrar aún más tiempo y realizar una búsqueda binaria desde el centro en los 4 lados (al menos reduciendo las interrogaciones de píxeles) –

+0

¿Puede la imagen pequeña e integrada tener píxeles transparentes dentro? –

Respuesta

23

La idea básica es comprobar cada píxel de la imagen para encontrar los límites superior, izquierdo, derecho e inferior de la imagen. Para hacer esto de manera eficiente, no use el método GetPixel, que es bastante lento. Use LockBits en su lugar.

Aquí está la aplicación que se me ocurrió:

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; 
} 

Probablemente se puede optimizar, pero yo no soy un GDI + expertos, por lo que es lo mejor que puedo hacer sin más investigación ...


EDIT: en realidad, no hay una forma sencilla de optimizarlo, al no escanear algunas partes de la imagen:

  1. exploración de izquierda a Righ t hasta que encuentre un píxel no transparente; almacenar (x, y) en (xMin, yMin)
  2. escanear de arriba abajo hasta que encuentre un píxel no transparente (solo para x> = xMin); almacenar y en yMin
  3. escanear de derecha a izquierda hasta que encuentre un píxel no transparente (solo para y> = yMin); almacenar x en xMax
  4. escanear de abajo arriba hasta que encuentre un píxel no transparente (solo para xMin < = x < = xMax); tienda y en yMax

Edit2: aquí está una implementación del enfoque anterior:

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; 
} 

No habrá una ganancia significativa si la parte no transparente es pequeño, por supuesto, ya todavía escaneará la mayoría de los píxeles. Pero si es grande, solo se explorarán los rectángulos que rodean la parte no transparente.

+0

Parece el enfoque más práctico. No lo veo mejor que esto. Buen consejo con el método LockBits también. +1 –

+2

Por cierto, me acabo de dar cuenta de que hay una manera más simple de recortar la imagen, sin utilizar un Graphics: 'return source.Clone (srcRect, source.PixelFormat);' –

+2

Gran solución, muy útil, pero encontré que mis imágenes eran ser recortado por un píxel de más. Lógicamente el suyo parece correcto, pero cambié la llamada a ** Rectangle.FromLTRB ** a ** srcRect = Rectangle.FromLTRB (xMin, yMin, xMax + 1, yMax + 1) ** y ahora funciona perfectamente. –

1

Me gustaría sugerir una división método & conquista:

  1. dividir la imagen en el medio (por ejemplo,verticalmente)
  2. verifique si hay píxeles no transparentes sobre la línea de corte (si es así, recuerde min/max para la caja)
  3. división izquierda media que limita de nuevo verticalmente
  4. si línea de corte contiene píxeles no transparentes -> actualización del cuadro delimitador
  5. si no, es probable que pueda desechar el medio más a la izquierda (no sé las fotos)
  6. continuar con la mitad izquierda-derecha (usted indicó que la imagen está en algún lugar en el medio) hasta que encuentre el extremo izquierdo de la imagen
  7. haga lo mismo con la mitad derecha
+3

Creo que su quinto punto es incorrecto: podría haber varias áreas distintas con píxeles no transparentes, por lo que el hecho de que no haya un píxel no transparente en la línea de corte no significa nada –

+0

Gracias bjoernz, pero sí: la búsqueda binaria no siempre funciona para mis imágenes, es posible que haya dos imágenes separadas por espacios en blanco, por ejemplo. – Blorgbeard

Cuestiones relacionadas