Автоматически обрезать растровое изображение до минимального размера? - PullRequest
11 голосов
/ 27 января 2011

Предположим, у меня есть System.Drawing.Bitmap в режиме ARGB 32bpp.Это большое растровое изображение, но в основном это полностью прозрачные пиксели с относительно небольшим изображением где-то посередине.

Что такое быстрый алгоритм для определения границ "реального" изображения, поэтому я могу вырезать всевокруг него прозрачные пиксели?

В качестве альтернативы, в .Net уже есть функция, которую я могу использовать для этого?

Ответы [ 2 ]

25 голосов
/ 27 января 2011

Основная идея - проверить каждый пиксель изображения, чтобы найти верхнюю, левую, правую и нижнюю границы изображения.Чтобы сделать это эффективно, не используйте метод 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 +, так что это лучшее, что я могуобойдемся без дальнейших исследований ...


РЕДАКТИРОВАТЬ: на самом деле, есть простой способ оптимизировать его, не сканируя некоторые части изображения:

  1. сканирование слева направопока не найдете непрозрачный пиксель;сохраняйте (x, y) в (xMin, yMin)
  2. сканирования сверху вниз, пока не найдете непрозрачный пиксель (только для x> = xMin);сохранить y в сканировании yMin
  3. справа налево, пока не найдете непрозрачный пиксель (только для y> = yMin);сохраните x в xMax
  4. сканирования снизу вверх, пока не найдете непрозрачный пиксель (только для xMin <= x <= xMax);сохранить y в yMax </li>

EDIT2: вот реализация подхода, описанного выше:

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

Не будет существенного усиления, если непрозрачная частьконечно, маленький, так как он все равно будет сканировать большинство пикселей.Но если он большой, будут сканироваться только прямоугольники вокруг непрозрачной части.

1 голос
/ 27 января 2011

Я хотел бы предложить подход «разделяй и властвуй»:

  1. разбить изображение по центру (например, по вертикали)
  2. проверить, есть ли непрозрачные пиксели на срезелиния (если это так, запомните мин / макс для ограничительной рамки)
  3. разделить левую половину снова по вертикали
  4. , если линия обрезки содержит непрозрачные пиксели -> обновить ограничительную рамку
  5. , еслинет, вы, вероятно, можете отбросить крайнюю левую половину (я не знаю фотографий)
  6. продолжить с левой и правой половиной (вы указали, что изображение находится где-то посередине), пока не найдете крайнюю левую границуизображение
  7. сделать то же самое для правой половины
...