Find last drawn pixel of C# Metafile
I have a Metafile object. For reasons outside of my control, it has been provided much larger (thousands of times larger) than what would be required to fit the image drawn inside it.
For example, it could be 40 000 x 40 000, yet only contains "real" (non-transparent) pixels in an area 2000 x 1600.
Originally, this metafile was simply drawn to a control, and the control bounds limited the area to a reasonable size.
Now I am trying to split it into different chunks of dynamic size, depending on user input. What I want to do it count how many of those chunks will be there (in x and in y, even the splitting is into a two-dimensional grid of chunks).
I am aware that, technically, I could go the O(N²) way, and just check the pixels one by one to find the "real" bounds of the drawn image.
But this will be painfully slow.
I am looking for a way of getting the position (x,y) of the very last drawn pixel in the entire metafile, without iterating through every single one of them.
Since The DrawImage method is not painfully slow, at least not N² slow, I assume that the metafile object has some optimisations on the inside that would allow something like this. Just like the List object has a .Count Property that is much faster than actually counting the objects, is there some way of getting the practical bounds of a metafile?
The drawn content, in this scenario, will always be rectangular. I can safely assume that the last pixel will be the same, whether I loop in x then y, or in y then x.
How can I find the coordinates of this "last" pixel?
c# graphics metafile
add a comment |
I have a Metafile object. For reasons outside of my control, it has been provided much larger (thousands of times larger) than what would be required to fit the image drawn inside it.
For example, it could be 40 000 x 40 000, yet only contains "real" (non-transparent) pixels in an area 2000 x 1600.
Originally, this metafile was simply drawn to a control, and the control bounds limited the area to a reasonable size.
Now I am trying to split it into different chunks of dynamic size, depending on user input. What I want to do it count how many of those chunks will be there (in x and in y, even the splitting is into a two-dimensional grid of chunks).
I am aware that, technically, I could go the O(N²) way, and just check the pixels one by one to find the "real" bounds of the drawn image.
But this will be painfully slow.
I am looking for a way of getting the position (x,y) of the very last drawn pixel in the entire metafile, without iterating through every single one of them.
Since The DrawImage method is not painfully slow, at least not N² slow, I assume that the metafile object has some optimisations on the inside that would allow something like this. Just like the List object has a .Count Property that is much faster than actually counting the objects, is there some way of getting the practical bounds of a metafile?
The drawn content, in this scenario, will always be rectangular. I can safely assume that the last pixel will be the same, whether I loop in x then y, or in y then x.
How can I find the coordinates of this "last" pixel?
c# graphics metafile
So by 'last' you mean the one 'most right-bottom'? Not the one drawn by the last WMF drawing command/shape?
– TaW
Nov 19 '18 at 19:07
If the shape was unknown, it might be hard to tell which is the last, but since I am garanteed a rectangular shape, by last I indeed mean the bottom right. If doing a loop over the width, and inside a loop over the height (or the reverse), it would be the last pixel checked that is not transparent. So yes, the last coordinates-wise, not drawing-order-wise
– Kaito Kid
Nov 19 '18 at 19:14
three things come to mind: a) don't look from top-left but from bottom-right. (you probably do that anyway) b) if you can draw the image scaled down by 1/10x or 1/100x you could get way with looking a a lot less pixels. c) if you can estimate the numbers you could draw the image shifted to the top-left and only look at the remainder. All three schemes should probably be combined..
– TaW
Nov 19 '18 at 19:52
@TaW this could easily be made into an answer
– Kaito Kid
Nov 26 '18 at 13:29
add a comment |
I have a Metafile object. For reasons outside of my control, it has been provided much larger (thousands of times larger) than what would be required to fit the image drawn inside it.
For example, it could be 40 000 x 40 000, yet only contains "real" (non-transparent) pixels in an area 2000 x 1600.
Originally, this metafile was simply drawn to a control, and the control bounds limited the area to a reasonable size.
Now I am trying to split it into different chunks of dynamic size, depending on user input. What I want to do it count how many of those chunks will be there (in x and in y, even the splitting is into a two-dimensional grid of chunks).
I am aware that, technically, I could go the O(N²) way, and just check the pixels one by one to find the "real" bounds of the drawn image.
But this will be painfully slow.
I am looking for a way of getting the position (x,y) of the very last drawn pixel in the entire metafile, without iterating through every single one of them.
Since The DrawImage method is not painfully slow, at least not N² slow, I assume that the metafile object has some optimisations on the inside that would allow something like this. Just like the List object has a .Count Property that is much faster than actually counting the objects, is there some way of getting the practical bounds of a metafile?
The drawn content, in this scenario, will always be rectangular. I can safely assume that the last pixel will be the same, whether I loop in x then y, or in y then x.
How can I find the coordinates of this "last" pixel?
c# graphics metafile
I have a Metafile object. For reasons outside of my control, it has been provided much larger (thousands of times larger) than what would be required to fit the image drawn inside it.
For example, it could be 40 000 x 40 000, yet only contains "real" (non-transparent) pixels in an area 2000 x 1600.
Originally, this metafile was simply drawn to a control, and the control bounds limited the area to a reasonable size.
Now I am trying to split it into different chunks of dynamic size, depending on user input. What I want to do it count how many of those chunks will be there (in x and in y, even the splitting is into a two-dimensional grid of chunks).
I am aware that, technically, I could go the O(N²) way, and just check the pixels one by one to find the "real" bounds of the drawn image.
But this will be painfully slow.
I am looking for a way of getting the position (x,y) of the very last drawn pixel in the entire metafile, without iterating through every single one of them.
Since The DrawImage method is not painfully slow, at least not N² slow, I assume that the metafile object has some optimisations on the inside that would allow something like this. Just like the List object has a .Count Property that is much faster than actually counting the objects, is there some way of getting the practical bounds of a metafile?
The drawn content, in this scenario, will always be rectangular. I can safely assume that the last pixel will be the same, whether I loop in x then y, or in y then x.
How can I find the coordinates of this "last" pixel?
c# graphics metafile
c# graphics metafile
edited Nov 19 '18 at 18:52
Kaito Kid
asked Nov 19 '18 at 18:27
Kaito KidKaito Kid
1961421
1961421
So by 'last' you mean the one 'most right-bottom'? Not the one drawn by the last WMF drawing command/shape?
– TaW
Nov 19 '18 at 19:07
If the shape was unknown, it might be hard to tell which is the last, but since I am garanteed a rectangular shape, by last I indeed mean the bottom right. If doing a loop over the width, and inside a loop over the height (or the reverse), it would be the last pixel checked that is not transparent. So yes, the last coordinates-wise, not drawing-order-wise
– Kaito Kid
Nov 19 '18 at 19:14
three things come to mind: a) don't look from top-left but from bottom-right. (you probably do that anyway) b) if you can draw the image scaled down by 1/10x or 1/100x you could get way with looking a a lot less pixels. c) if you can estimate the numbers you could draw the image shifted to the top-left and only look at the remainder. All three schemes should probably be combined..
– TaW
Nov 19 '18 at 19:52
@TaW this could easily be made into an answer
– Kaito Kid
Nov 26 '18 at 13:29
add a comment |
So by 'last' you mean the one 'most right-bottom'? Not the one drawn by the last WMF drawing command/shape?
– TaW
Nov 19 '18 at 19:07
If the shape was unknown, it might be hard to tell which is the last, but since I am garanteed a rectangular shape, by last I indeed mean the bottom right. If doing a loop over the width, and inside a loop over the height (or the reverse), it would be the last pixel checked that is not transparent. So yes, the last coordinates-wise, not drawing-order-wise
– Kaito Kid
Nov 19 '18 at 19:14
three things come to mind: a) don't look from top-left but from bottom-right. (you probably do that anyway) b) if you can draw the image scaled down by 1/10x or 1/100x you could get way with looking a a lot less pixels. c) if you can estimate the numbers you could draw the image shifted to the top-left and only look at the remainder. All three schemes should probably be combined..
– TaW
Nov 19 '18 at 19:52
@TaW this could easily be made into an answer
– Kaito Kid
Nov 26 '18 at 13:29
So by 'last' you mean the one 'most right-bottom'? Not the one drawn by the last WMF drawing command/shape?
– TaW
Nov 19 '18 at 19:07
So by 'last' you mean the one 'most right-bottom'? Not the one drawn by the last WMF drawing command/shape?
– TaW
Nov 19 '18 at 19:07
If the shape was unknown, it might be hard to tell which is the last, but since I am garanteed a rectangular shape, by last I indeed mean the bottom right. If doing a loop over the width, and inside a loop over the height (or the reverse), it would be the last pixel checked that is not transparent. So yes, the last coordinates-wise, not drawing-order-wise
– Kaito Kid
Nov 19 '18 at 19:14
If the shape was unknown, it might be hard to tell which is the last, but since I am garanteed a rectangular shape, by last I indeed mean the bottom right. If doing a loop over the width, and inside a loop over the height (or the reverse), it would be the last pixel checked that is not transparent. So yes, the last coordinates-wise, not drawing-order-wise
– Kaito Kid
Nov 19 '18 at 19:14
three things come to mind: a) don't look from top-left but from bottom-right. (you probably do that anyway) b) if you can draw the image scaled down by 1/10x or 1/100x you could get way with looking a a lot less pixels. c) if you can estimate the numbers you could draw the image shifted to the top-left and only look at the remainder. All three schemes should probably be combined..
– TaW
Nov 19 '18 at 19:52
three things come to mind: a) don't look from top-left but from bottom-right. (you probably do that anyway) b) if you can draw the image scaled down by 1/10x or 1/100x you could get way with looking a a lot less pixels. c) if you can estimate the numbers you could draw the image shifted to the top-left and only look at the remainder. All three schemes should probably be combined..
– TaW
Nov 19 '18 at 19:52
@TaW this could easily be made into an answer
– Kaito Kid
Nov 26 '18 at 13:29
@TaW this could easily be made into an answer
– Kaito Kid
Nov 26 '18 at 13:29
add a comment |
1 Answer
1
active
oldest
votes
Finding the bounding rectangle of the non-transparent pixels for such a large image is indeed an interesting challenge.
The most direct approach would be tackling the WMF content but that is also by far the hardest to get right.
Let's instead render the image to a bitmap and look at the bitmap.
First the basic approach, then a few optimizations.
To get the bounds one need to find the left, top, right
and bottom
borders.
Here is a simple function to do that:
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);
}
Note that is optimizes the last, vertical loops a little to look only at the portion not already tested by the horizontal loops.
It uses GetPixel
, which is painfully slow; but even Lockbits
only gains 'only' about 10x or so. So we need to reduce the sheer numbers; we need to do that anyway, because 40k x 40k pixels is too large for a Bitmap
.
Since WMF is usually filled with vector data we probably can scale it down a lot. Here is an example:
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 ));
}
Note that to properly draw the wmf file one may need to adapt the resolutions. Here is an example i used for testing:
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);
}
I left this out but for fully controlling the rendering, it ought have been included in the snippet above as well..
This may or may not be good enough, depending on the accuracy needed.
To measure the bounds perfectly one can do this trick: Use the bounds from the scaled down test and measure unscaled but only a tiny stripe around the four bound numbers. When creating the render bitmap we move the origin accordingly.
Example for the right bound:
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);
}
I could have optimized by not going over the full height but only the portion (plus delte) we already found..
Further optimization can be achieved if one can use knowledge about the data. If we know that the image data are connected we could use larger steps in the loops until a pixel is found and then trace back one step..
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53380617%2ffind-last-drawn-pixel-of-c-sharp-metafile%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
Finding the bounding rectangle of the non-transparent pixels for such a large image is indeed an interesting challenge.
The most direct approach would be tackling the WMF content but that is also by far the hardest to get right.
Let's instead render the image to a bitmap and look at the bitmap.
First the basic approach, then a few optimizations.
To get the bounds one need to find the left, top, right
and bottom
borders.
Here is a simple function to do that:
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);
}
Note that is optimizes the last, vertical loops a little to look only at the portion not already tested by the horizontal loops.
It uses GetPixel
, which is painfully slow; but even Lockbits
only gains 'only' about 10x or so. So we need to reduce the sheer numbers; we need to do that anyway, because 40k x 40k pixels is too large for a Bitmap
.
Since WMF is usually filled with vector data we probably can scale it down a lot. Here is an example:
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 ));
}
Note that to properly draw the wmf file one may need to adapt the resolutions. Here is an example i used for testing:
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);
}
I left this out but for fully controlling the rendering, it ought have been included in the snippet above as well..
This may or may not be good enough, depending on the accuracy needed.
To measure the bounds perfectly one can do this trick: Use the bounds from the scaled down test and measure unscaled but only a tiny stripe around the four bound numbers. When creating the render bitmap we move the origin accordingly.
Example for the right bound:
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);
}
I could have optimized by not going over the full height but only the portion (plus delte) we already found..
Further optimization can be achieved if one can use knowledge about the data. If we know that the image data are connected we could use larger steps in the loops until a pixel is found and then trace back one step..
add a comment |
Finding the bounding rectangle of the non-transparent pixels for such a large image is indeed an interesting challenge.
The most direct approach would be tackling the WMF content but that is also by far the hardest to get right.
Let's instead render the image to a bitmap and look at the bitmap.
First the basic approach, then a few optimizations.
To get the bounds one need to find the left, top, right
and bottom
borders.
Here is a simple function to do that:
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);
}
Note that is optimizes the last, vertical loops a little to look only at the portion not already tested by the horizontal loops.
It uses GetPixel
, which is painfully slow; but even Lockbits
only gains 'only' about 10x or so. So we need to reduce the sheer numbers; we need to do that anyway, because 40k x 40k pixels is too large for a Bitmap
.
Since WMF is usually filled with vector data we probably can scale it down a lot. Here is an example:
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 ));
}
Note that to properly draw the wmf file one may need to adapt the resolutions. Here is an example i used for testing:
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);
}
I left this out but for fully controlling the rendering, it ought have been included in the snippet above as well..
This may or may not be good enough, depending on the accuracy needed.
To measure the bounds perfectly one can do this trick: Use the bounds from the scaled down test and measure unscaled but only a tiny stripe around the four bound numbers. When creating the render bitmap we move the origin accordingly.
Example for the right bound:
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);
}
I could have optimized by not going over the full height but only the portion (plus delte) we already found..
Further optimization can be achieved if one can use knowledge about the data. If we know that the image data are connected we could use larger steps in the loops until a pixel is found and then trace back one step..
add a comment |
Finding the bounding rectangle of the non-transparent pixels for such a large image is indeed an interesting challenge.
The most direct approach would be tackling the WMF content but that is also by far the hardest to get right.
Let's instead render the image to a bitmap and look at the bitmap.
First the basic approach, then a few optimizations.
To get the bounds one need to find the left, top, right
and bottom
borders.
Here is a simple function to do that:
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);
}
Note that is optimizes the last, vertical loops a little to look only at the portion not already tested by the horizontal loops.
It uses GetPixel
, which is painfully slow; but even Lockbits
only gains 'only' about 10x or so. So we need to reduce the sheer numbers; we need to do that anyway, because 40k x 40k pixels is too large for a Bitmap
.
Since WMF is usually filled with vector data we probably can scale it down a lot. Here is an example:
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 ));
}
Note that to properly draw the wmf file one may need to adapt the resolutions. Here is an example i used for testing:
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);
}
I left this out but for fully controlling the rendering, it ought have been included in the snippet above as well..
This may or may not be good enough, depending on the accuracy needed.
To measure the bounds perfectly one can do this trick: Use the bounds from the scaled down test and measure unscaled but only a tiny stripe around the four bound numbers. When creating the render bitmap we move the origin accordingly.
Example for the right bound:
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);
}
I could have optimized by not going over the full height but only the portion (plus delte) we already found..
Further optimization can be achieved if one can use knowledge about the data. If we know that the image data are connected we could use larger steps in the loops until a pixel is found and then trace back one step..
Finding the bounding rectangle of the non-transparent pixels for such a large image is indeed an interesting challenge.
The most direct approach would be tackling the WMF content but that is also by far the hardest to get right.
Let's instead render the image to a bitmap and look at the bitmap.
First the basic approach, then a few optimizations.
To get the bounds one need to find the left, top, right
and bottom
borders.
Here is a simple function to do that:
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);
}
Note that is optimizes the last, vertical loops a little to look only at the portion not already tested by the horizontal loops.
It uses GetPixel
, which is painfully slow; but even Lockbits
only gains 'only' about 10x or so. So we need to reduce the sheer numbers; we need to do that anyway, because 40k x 40k pixels is too large for a Bitmap
.
Since WMF is usually filled with vector data we probably can scale it down a lot. Here is an example:
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 ));
}
Note that to properly draw the wmf file one may need to adapt the resolutions. Here is an example i used for testing:
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);
}
I left this out but for fully controlling the rendering, it ought have been included in the snippet above as well..
This may or may not be good enough, depending on the accuracy needed.
To measure the bounds perfectly one can do this trick: Use the bounds from the scaled down test and measure unscaled but only a tiny stripe around the four bound numbers. When creating the render bitmap we move the origin accordingly.
Example for the right bound:
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);
}
I could have optimized by not going over the full height but only the portion (plus delte) we already found..
Further optimization can be achieved if one can use knowledge about the data. If we know that the image data are connected we could use larger steps in the loops until a pixel is found and then trace back one step..
edited Dec 3 '18 at 15:20
answered Nov 29 '18 at 11:23


TaWTaW
40.9k62762
40.9k62762
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53380617%2ffind-last-drawn-pixel-of-c-sharp-metafile%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
So by 'last' you mean the one 'most right-bottom'? Not the one drawn by the last WMF drawing command/shape?
– TaW
Nov 19 '18 at 19:07
If the shape was unknown, it might be hard to tell which is the last, but since I am garanteed a rectangular shape, by last I indeed mean the bottom right. If doing a loop over the width, and inside a loop over the height (or the reverse), it would be the last pixel checked that is not transparent. So yes, the last coordinates-wise, not drawing-order-wise
– Kaito Kid
Nov 19 '18 at 19:14
three things come to mind: a) don't look from top-left but from bottom-right. (you probably do that anyway) b) if you can draw the image scaled down by 1/10x or 1/100x you could get way with looking a a lot less pixels. c) if you can estimate the numbers you could draw the image shifted to the top-left and only look at the remainder. All three schemes should probably be combined..
– TaW
Nov 19 '18 at 19:52
@TaW this could easily be made into an answer
– Kaito Kid
Nov 26 '18 at 13:29