Найти ограничивающий прямоугольник непрозрачных пикселей для такого большого изображения - действительно интересная задача.
Самым прямым подходом было бы заняться контентом WMF, но это также намного сложнее понять.
Давайте вместо этого отрендерим изображение в растровое изображение и посмотрим на растровое изображение.
Сначала базовый подход, затем несколько оптимизаций.
Чтобы получить границы, нужно найти границы l eft, top, right
и bottom
.
Вот простая функция для этого:
Rectangle getBounds(Bitmap bmp)
{
int l, r, t, b; l = t = r = b = 0;
for (int x = 0; x < bmp.Width - 1; x++)
for (int y = 0; y < bmp.Height - 1; y++)
if (bmp.GetPixel(x,y).A > 0) { l = x; goto l1; }
l1:
for (int x = bmp.Width - 1; x > l ; x--)
for (int y = 0; y < bmp.Height - 1; y++)
if (bmp.GetPixel(x,y).A > 0) { r = x; goto l2; }
l2:
for (int y = 0; y < bmp.Height - 1; y++)
for (int x = l; x < r; x++)
if (bmp.GetPixel(x,y).A > 0) { t = y; goto l3; }
l3:
for (int y = bmp.Height - 1; y > t; y--)
for (int x = l; x < r; x++)
if (bmp.GetPixel(x,y).A > 0) { b = y; goto l4; }
l4:
return Rectangle.FromLTRB(l,t,r,b);
}
Обратите внимание, что оптимизирует последние вертикальные петли немного, чтобы смотреть только на часть, еще не проверенную горизонтальными петлями.
Используется GetPixel
, что мучительно медленно; но даже Lockbits
получает только «только» примерно в 10 раз или около того. Таким образом, мы должны уменьшить количество цифр; мы все равно должны это сделать, потому что 40k x 40k пикселей слишком велико для Bitmap
.
Поскольку WMF обычно заполняется векторными данными, мы, вероятно, можем значительно уменьшить его. Вот пример:
string fn = "D:\\_test18b.emf";
Image img = Image.FromFile(fn);
int w = img.Width;
int h = img.Height;
float scale = 100;
Rectangle rScaled = Rectangle.Empty;
using (Bitmap bmp = new Bitmap((int)(w / scale), (int)(h / scale)))
using (Graphics g = Graphics.FromImage(bmp))
{
g.ScaleTransform(1f/scale, 1f/scale);
g.Clear(Color.Transparent);
g.DrawImage(img, 0, 0);
rScaled = getBounds(bmp);
Rectangle rUnscaled = Rectangle.Round(
new RectangleF(rScaled.Left * scale, rScaled.Top * scale,
rScaled.Width * scale, rScaled.Height * scale ));
}
Обратите внимание, что для правильного отображения файла wmf может потребоваться адаптировать разрешения. Вот пример, который я использовал для тестирования:
using (Graphics g2 = pictureBox.CreateGraphics())
{
float scaleX = g2.DpiX / img.HorizontalResolution / scale;
float scaleY = g2.DpiY / img.VerticalResolution / scale;
g2.ScaleTransform(scaleX, scaleY);
g2.DrawImage(img, 0, 0); // draw the original emf image.. (*)
g2.ResetTransform();
// g2.DrawImage(bmp, 0, 0); // .. it will look the same as (*)
g2.DrawRectangle(Pens.Black, rScaled);
}
Я пропустил это, но для полного контроля над рендерингом, он должен быть также включен в приведенный выше фрагмент кода.
Это может быть или не быть достаточно хорошим, в зависимости от необходимой точности.
Чтобы точно измерить границы, можно сделать следующий трюк: использовать границы из уменьшенного теста и измерить немасштабированное, но только крошечную полоску вокруг четырех связанных чисел. При создании растрового изображения мы соответственно перемещаем источник.
Пример для правой границы:
Rectangle rScaled2 = Rectangle.Empty;
int delta = 80;
int right = (int)(rScaled.Right * scale);
using (Bitmap bmp = new Bitmap((int)(delta * 2 ), (int)(h )))
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Transparent);
g.DrawImage(img, - right - delta, 0);
rScaled2 = getBounds(bmp);
}
Я мог бы оптимизировать, не перебирая всю высоту, а только ту часть (плюс дельта), которую мы уже нашли ..
Дальнейшая оптимизация может быть достигнута, если можно использовать знания о данных. Если мы знаем, что данные изображения связаны , мы можем использовать более крупные шаги в циклах, пока не будет найден пиксель, а затем проследить один шаг назад.