When I take screenshots with ChromeDriver I get screens with the size of my viewport.
When I take screenshots with FirefoxDriver I get what I want, which is a full screen print of a website.
ChromeDriver is declared like this:
IWebDriver driver = new ChromeDriver();
FirefoxDriver is declared like this:
IWebDriver driver = new FirefoxDriver();
Both drivers execute identical code:
driver.Manage().Window.Maximize();
driver.Navigate().GoToUrl(url);//url is a string variable
ITakesScreenshot screenshotDriver = driver as ITakesScreenshot;
Screenshot screenshot = screenshotDriver.GetScreenshot();
screenshot.SaveAsFile("c:/test.png", ImageFormat.Png);
ChromeDriver's test.png is of 1920x1099 resolution and contains only the browser viewport.
FirefoxDriver's test.png is of 1903x16559 resolution and contains the whole page.
I know that GetScreenshot() method doesn't return identical resolution sizes because it has slightly different implementations in IEDriver, FirefoxDriver, OperaDriver, ChromeDriver.
My questions are:
Why is there such difference between ChromeDriver's and FirefoxDriver's .GetScreenshot() method, even tho they use an identical interface (ITakesScreenshot)?
Is there a way to make ChromeDriver's GetScreenshot() method return the whole webpage screen instead of just the viewport?
we can't get the entire page screenshot with ChromeDriver2, we need to go for manual implementation.I have modified a method with is available in a blog which works fine with ChromeDriver.
use this method as following :
private IWebDriver _driver = new ChromeDriver(CHROME_DRIVER_PATH);
screenshot.SaveAsFile(saveFileName, ImageFormat.Jpeg);
public Bitmap GetEntereScreenshot()
{
Bitmap stitchedImage = null;
try
{
long totalwidth1 = (long)((IJavaScriptExecutor)_driver).ExecuteScript("return document.body.offsetWidth");//documentElement.scrollWidth");
long totalHeight1 = (long)((IJavaScriptExecutor)_driver).ExecuteScript("return document.body.parentNode.scrollHeight");
int totalWidth = (int)totalwidth1;
int totalHeight = (int)totalHeight1;
// Get the Size of the Viewport
long viewportWidth1 = (long)((IJavaScriptExecutor)_driver).ExecuteScript("return document.body.clientWidth");//documentElement.scrollWidth");
long viewportHeight1 = (long)((IJavaScriptExecutor)_driver).ExecuteScript("return window.innerHeight");//documentElement.scrollWidth");
int viewportWidth = (int)viewportWidth1;
int viewportHeight = (int)viewportHeight1;
// Split the Screen in multiple Rectangles
List<Rectangle> rectangles = new List<Rectangle>();
// Loop until the Total Height is reached
for (int i = 0; i < totalHeight; i += viewportHeight)
{
int newHeight = viewportHeight;
// Fix if the Height of the Element is too big
if (i + viewportHeight > totalHeight)
{
newHeight = totalHeight - i;
}
// Loop until the Total Width is reached
for (int ii = 0; ii < totalWidth; ii += viewportWidth)
{
int newWidth = viewportWidth;
// Fix if the Width of the Element is too big
if (ii + viewportWidth > totalWidth)
{
newWidth = totalWidth - ii;
}
// Create and add the Rectangle
Rectangle currRect = new Rectangle(ii, i, newWidth, newHeight);
rectangles.Add(currRect);
}
}
// Build the Image
stitchedImage = new Bitmap(totalWidth, totalHeight);
// Get all Screenshots and stitch them together
Rectangle previous = Rectangle.Empty;
foreach (var rectangle in rectangles)
{
// Calculate the Scrolling (if needed)
if (previous != Rectangle.Empty)
{
int xDiff = rectangle.Right - previous.Right;
int yDiff = rectangle.Bottom - previous.Bottom;
// Scroll
//selenium.RunScript(String.Format("window.scrollBy({0}, {1})", xDiff, yDiff));
((IJavaScriptExecutor)_driver).ExecuteScript(String.Format("window.scrollBy({0}, {1})", xDiff, yDiff));
System.Threading.Thread.Sleep(200);
}
// Take Screenshot
var screenshot = ((ITakesScreenshot)_driver).GetScreenshot();
// Build an Image out of the Screenshot
Image screenshotImage;
using (MemoryStream memStream = new MemoryStream(screenshot.AsByteArray))
{
screenshotImage = Image.FromStream(memStream);
}
// Calculate the Source Rectangle
Rectangle sourceRectangle = new Rectangle(viewportWidth - rectangle.Width, viewportHeight - rectangle.Height, rectangle.Width, rectangle.Height);
// Copy the Image
using (Graphics g = Graphics.FromImage(stitchedImage))
{
g.DrawImage(screenshotImage, rectangle, sourceRectangle, GraphicsUnit.Pixel);
}
// Set the Previous Rectangle
previous = rectangle;
}
}
catch (Exception ex)
{
// handle
}
return stitchedImage;
}
I cleaned up #Selvantharajah Roshanth's answer and added a check so that it won't try to stitch together screenshots that already fit in the viewport.
public Image GetEntireScreenshot()
{
// Get the total size of the page
var totalWidth = (int) (long) ((IJavaScriptExecutor) driver).ExecuteScript("return document.body.offsetWidth"); //documentElement.scrollWidth");
var totalHeight = (int) (long) ((IJavaScriptExecutor) driver).ExecuteScript("return document.body.parentNode.scrollHeight");
// Get the size of the viewport
var viewportWidth = (int) (long) ((IJavaScriptExecutor) driver).ExecuteScript("return document.body.clientWidth"); //documentElement.scrollWidth");
var viewportHeight = (int) (long) ((IJavaScriptExecutor) driver).ExecuteScript("return window.innerHeight"); //documentElement.scrollWidth");
// We only care about taking multiple images together if it doesn't already fit
if (totalWidth <= viewportWidth && totalHeight <= viewportHeight)
{
var screenshot = driver.TakeScreenshot();
return ScreenshotToImage(screenshot);
}
// Split the screen in multiple Rectangles
var rectangles = new List<Rectangle>();
// Loop until the totalHeight is reached
for (var y = 0; y < totalHeight; y += viewportHeight)
{
var newHeight = viewportHeight;
// Fix if the height of the element is too big
if (y + viewportHeight > totalHeight)
{
newHeight = totalHeight - y;
}
// Loop until the totalWidth is reached
for (var x = 0; x < totalWidth; x += viewportWidth)
{
var newWidth = viewportWidth;
// Fix if the Width of the Element is too big
if (x + viewportWidth > totalWidth)
{
newWidth = totalWidth - x;
}
// Create and add the Rectangle
var currRect = new Rectangle(x, y, newWidth, newHeight);
rectangles.Add(currRect);
}
}
// Build the Image
var stitchedImage = new Bitmap(totalWidth, totalHeight);
// Get all Screenshots and stitch them together
var previous = Rectangle.Empty;
foreach (var rectangle in rectangles)
{
// Calculate the scrolling (if needed)
if (previous != Rectangle.Empty)
{
var xDiff = rectangle.Right - previous.Right;
var yDiff = rectangle.Bottom - previous.Bottom;
// Scroll
((IJavaScriptExecutor) driver).ExecuteScript(String.Format("window.scrollBy({0}, {1})", xDiff, yDiff));
}
// Take Screenshot
var screenshot = driver.TakeScreenshot();
// Build an Image out of the Screenshot
var screenshotImage = ScreenshotToImage(screenshot);
// Calculate the source Rectangle
var sourceRectangle = new Rectangle(viewportWidth - rectangle.Width, viewportHeight - rectangle.Height, rectangle.Width, rectangle.Height);
// Copy the Image
using (var graphics = Graphics.FromImage(stitchedImage))
{
graphics.DrawImage(screenshotImage, rectangle, sourceRectangle, GraphicsUnit.Pixel);
}
// Set the Previous Rectangle
previous = rectangle;
}
return stitchedImage;
}
private static Image ScreenshotToImage(Screenshot screenshot)
{
Image screenshotImage;
using (var memStream = new MemoryStream(screenshot.AsByteArray))
{
screenshotImage = Image.FromStream(memStream);
}
return screenshotImage;
}
It appears as though full-screen screenshots are not yet implemented in the ChromeDriver, due to some inaccuracies in its previous implementation.
Source: https://code.google.com/p/chromedriver/issues/detail?id=294
I have recently written a Selenium based application to test an Internet Explorer UI and found that:
Taking screenshots with selenium was not as quick as using .NET, and
Selenium is unable to take screenshots when dialog boxes are present. This was a major drawback, as I needed to identify unexpected dialogs during interaction with the pages.
Investigate using the Graphics.CopyFromScreen method in System.Drawing as an alternative solution until the feature is implemented in Chrome. Once you have tried .the Net approach however, I don't think you will look back =]
I stumbled accross the same problem and ChromeDriver2 just does not support it.
So I created a little script which scrolls thru the page, takes screenshots and stitches everything together.
You can find the script in my blog post here:
http://dev.flauschig.ch/wordpress/?p=341
Related
I have black image with white lines. Is it possible to exclude chunks of whihte pixels, that are smaller than specific number? For example: change color of chunks of pixels that are made from less than 10 pixels from white to black.
Original Image:
Image on the output(small areas of white pixels are removed):
Right now I work with AForge library for C#, but C++ ways of solving this are also apreciated(Open CV, for example). And hint, on how this functionality might be called are also appreciated.
Without worrying to much about your details, it does seem trivially simple
Use bitmap in 32bits and use LockBits to get scanlines and direct pointer access to the array.
Scan every pixel with 2 for loops
Every time you find one that matches your target color, scan left right and up and down (X) Amount of pixels to determine if it matches your requirements,
If it does, leave the pixel, if not change it.
if you wanted more speed you could chuck this all in a parallel workload, also there is probably more you could do with a mask array to save you researching dead paths (just a thought)
Note, Obviously you can smarten this up a bit
Exmaple
// lock the array for direct access
var bitmapData = bitmap.LockBits(Bounds, ImageLockMode.ReadWrite, Bitmap.PixelFormat);
// get the pointer
var scan0Ptr = (int*)_bitmapData.Scan0;
// get the stride
var stride = _bitmapData.Stride / BytesPerPixel;
// local method
void Workload(Rectangle bounds)
{
// this is if synchronous, Bounds is just the full image rectangle
var rect = bounds ?? Bounds;
var white = Color.White.ToArgb();
var black = Color.Black.ToArgb();
// scan all x
for (var x = rect.Left; x < rect.Right; x++)
{
var pX = scan0Ptr + x;
// scan all y
for (var y = rect.Top; y < rect.Bottom; y++)
{
if (*(pX + y * stride ) != white)
{
// this will turn it to monochrome
// so add your threshold here, ie some more for loops
//*(pX + y * Stride) = black;
}
}
}
}
// unlock the bitmap
bitmap.UnlockBits(_bitmapData);
To parallel'ize it
You could use something like this to break your image up into smaller regions
public static List<Rectangle> GetSubRects(this Rectangle source, int size)
{
var rects = new List<Rectangle>();
for (var x = 0; x < size; x++)
{
var width = Convert.ToInt32(Math.Floor(source.Width / (double)size));
var xCal = 0;
if (x == size - 1)
{
xCal = source.Width - (width * size);
}
for (var y = 0; y < size; y++)
{
var height = Convert.ToInt32(Math.Floor(source.Height / (double)size));
var yCal = 0;
if (y == size - 1)
{
yCal = source.Height - (height * size) ;
}
rects.Add(new Rectangle(width * x, height * y, width+ xCal, height + yCal));
}
}
return rects;
}
And this
private static void DoWorkload(Rectangle bounds, ParallelOptions options, Action<Rectangle?> workload)
{
if (options == null)
{
workload(null);
}
else
{
var size = 5 // how many rects to work on, ie 5 x 5
Parallel.ForEach(bounds.GetSubRects(size), options, rect => workload(rect));
}
}
Usage
DoWorkload(Bounds, options, Workload);
I have a windows forms project written in C#. The main form has a TabControl on it and there is a requirement for one of the users to be able to print one of the TabPages. The form is very long and I use a vertical scroll bar. The whole of the form needs to be able to be printed.
I have tried using the DrawToBitmap method to convert to a bitmap first, but this will only include the portion of the form that the user can see. Some other solutions I have tried involve screen capturing, which has the same issue.
How can I print out, or get an image, of the whole of the tab page, including the parts the user only sees when they scroll down?
This is rather simple for any control including TabControls and TabPages but not Forms.
All you need to do is enlarge the relevant controls enough to show all their content. (They don't have to be actually visible on screen.)
Here is an example:
tabControl1.Height = 10080;
tabPage2.Height = 10050;
dataGridView1.Height = 10000;
dataGridView1.Rows.Add(3000);
for (int i = 0; i < dataGridView1.Rows.Count; i++) dataGridView1[0, i].Value = i;
using (Bitmap bmp = new Bitmap(tabControl1.Width , tabControl1.Height ))
{
tabControl1.DrawToBitmap(bmp, tabControl1.ClientRectangle);
bmp.Save("D:\\xxxx.png", ImageFormat.Png);
}
This saves the full content of the DataGridView, the TabPage and the TabControl..
Note: that this will not work with forms, which can't much exceed the screen dimensions..
Update: Here is code that saves a form with vertical scrolling by patching several bitmaps together. It can, of course be expanded to include horizontal scrolling as well. I have coded a similar solution for larger Panels here.
static void saveLargeForm(Form form, string fileName)
{
// yes it may take a while
form.Cursor = Cursors.WaitCursor;
// allocate target bitmap and a buffer bitmap
Bitmap target = new Bitmap(form.DisplayRectangle.Width, form.DisplayRectangle.Height);
Bitmap buffer = new Bitmap(form.Width, form.Height);
// the vertical pointer
int y = 0;
var vsc = form.VerticalScroll;
vsc.Value = 0;
form.AutoScrollPosition = new Point(0, 0);
// the scroll amount
int l = vsc.LargeChange;
Rectangle srcRect = ClientBounds(form);
Rectangle destRect = Rectangle.Empty;
bool done = false;
// we'll draw onto the large bitmap with G
using (Graphics G = Graphics.FromImage(target))
{
while (!done)
{
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
int v = vsc.Value;
vsc.Value = vsc.Value + l;
form.AutoScrollPosition = new Point(form.AutoScrollPosition.X, vsc.Value + l);
int delta = vsc.Value - v;
done = delta < l;
y += delta;
}
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
}
// write result to disc and clean up
target.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
target.Dispose();
buffer.Dispose();
GC.Collect(); // not sure why, but it helped
form.Cursor = Cursors.Default;
}
It makes use of a helper function to determine the the net size of the virtual client rectangle, ie excluding borders, title and scrollbar:
static Rectangle ClientBounds(Form f)
{
Rectangle rc = f.ClientRectangle;
Rectangle rb = f.Bounds;
int sw = SystemInformation.VerticalScrollBarWidth;
var vsc = f.VerticalScroll;
int bw = (rb.Width - rc.Width - (vsc.Visible ? sw : 0) ) / 2;
int th = (rb.Height - rc.Height) - bw * 2;
return new Rectangle(bw, th + bw, rc.Width, rc.Height );
}
I am new to Xamarin android and for below code am getting OutOfMemoryException. Here profilebitMap is a Bitmap and mProfileImage is an ImageView. I have tried this with using block and dispose/recycle methods also but still getting the same error after multiple returns to the image page. Please help me in this.
if (!string.IsNullOrEmpty(profile.Image))
{
string[] fileExtension = profile.Image.Split('/');
string _imagePath = System.IO.Path.Combine(_documentsPath.ToString(), profile.ID + fileExtension[fileExtension.Length - 1]);
if (File.Exists(_imagePath))
{
profilebitMap = await BitmapFactory.DecodeFileAsync(_imagePath);
//profilebitMap = Util.Base64ToBitmap(appPreferencce.getAccessKey(profile.Image));
}
else
{
profilebitMap = Util.GetImageBitmapFromUrl(profile.Image, appPreferencce.getAccessKey("username"), appPreferencce.getAccessKey("password"));
using (var stream = new MemoryStream())
{
profilebitMap.Compress(Bitmap.CompressFormat.Png, 100, stream);
var imageBytes = stream.ToArray();
File.WriteAllBytes(_imagePath, imageBytes);
}
}
}
else
{
profilebitMap = BitmapFactory.DecodeResource(this.ApplicationContext.Resources, Resource.Drawable.dummyuser);
}
CircularDrawable d = new CircularDrawable(profilebitMap,
(int)Util.ConvertDpToPx(ApplicationContext, margin),
Util.ConvertDpToPx(ApplicationContext, strokeWidth),
new Android.Graphics.Color(ContextCompat.GetColor(this, Resource.Color.normal3)));
mProfileImage.SetBackgroundDrawable(d);
You don't have to reinvent the wheel, you can use libraries that load you pictures in few lines. You should take a look to the awesome library Picasso.
Bye
To avoid the OutOfMemoryException we should use BitmapFactory.Options class.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object but setting outWidth, outHeight and outMimeType.
Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead.
For example, an image with resolution 2048x1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image.
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Please follow this url https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
I have a windows forms project written in C#. The main form has a TabControl on it and there is a requirement for one of the users to be able to print one of the TabPages. The form is very long and I use a vertical scroll bar. The whole of the form needs to be able to be printed.
I have tried using the DrawToBitmap method to convert to a bitmap first, but this will only include the portion of the form that the user can see. Some other solutions I have tried involve screen capturing, which has the same issue.
How can I print out, or get an image, of the whole of the tab page, including the parts the user only sees when they scroll down?
This is rather simple for any control including TabControls and TabPages but not Forms.
All you need to do is enlarge the relevant controls enough to show all their content. (They don't have to be actually visible on screen.)
Here is an example:
tabControl1.Height = 10080;
tabPage2.Height = 10050;
dataGridView1.Height = 10000;
dataGridView1.Rows.Add(3000);
for (int i = 0; i < dataGridView1.Rows.Count; i++) dataGridView1[0, i].Value = i;
using (Bitmap bmp = new Bitmap(tabControl1.Width , tabControl1.Height ))
{
tabControl1.DrawToBitmap(bmp, tabControl1.ClientRectangle);
bmp.Save("D:\\xxxx.png", ImageFormat.Png);
}
This saves the full content of the DataGridView, the TabPage and the TabControl..
Note: that this will not work with forms, which can't much exceed the screen dimensions..
Update: Here is code that saves a form with vertical scrolling by patching several bitmaps together. It can, of course be expanded to include horizontal scrolling as well. I have coded a similar solution for larger Panels here.
static void saveLargeForm(Form form, string fileName)
{
// yes it may take a while
form.Cursor = Cursors.WaitCursor;
// allocate target bitmap and a buffer bitmap
Bitmap target = new Bitmap(form.DisplayRectangle.Width, form.DisplayRectangle.Height);
Bitmap buffer = new Bitmap(form.Width, form.Height);
// the vertical pointer
int y = 0;
var vsc = form.VerticalScroll;
vsc.Value = 0;
form.AutoScrollPosition = new Point(0, 0);
// the scroll amount
int l = vsc.LargeChange;
Rectangle srcRect = ClientBounds(form);
Rectangle destRect = Rectangle.Empty;
bool done = false;
// we'll draw onto the large bitmap with G
using (Graphics G = Graphics.FromImage(target))
{
while (!done)
{
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
int v = vsc.Value;
vsc.Value = vsc.Value + l;
form.AutoScrollPosition = new Point(form.AutoScrollPosition.X, vsc.Value + l);
int delta = vsc.Value - v;
done = delta < l;
y += delta;
}
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
}
// write result to disc and clean up
target.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
target.Dispose();
buffer.Dispose();
GC.Collect(); // not sure why, but it helped
form.Cursor = Cursors.Default;
}
It makes use of a helper function to determine the the net size of the virtual client rectangle, ie excluding borders, title and scrollbar:
static Rectangle ClientBounds(Form f)
{
Rectangle rc = f.ClientRectangle;
Rectangle rb = f.Bounds;
int sw = SystemInformation.VerticalScrollBarWidth;
var vsc = f.VerticalScroll;
int bw = (rb.Width - rc.Width - (vsc.Visible ? sw : 0) ) / 2;
int th = (rb.Height - rc.Height) - bw * 2;
return new Rectangle(bw, th + bw, rc.Width, rc.Height );
}
I've written a small "watermark" program to add a custom watermark to an image. There are two watermarks a white one and a black one. The watermark is always placed on the left bottom side of the image. I clone that region of the image to determine which watermark should be placed based on that spot (the black watermark on light regions and the white watermark on dark regions).
When I use the application (either in debug or normal) on my machine - no problem. All images are processed and watermarks are added on the correct location.
However, on the client machine, the program breaks on all images throwing a OutOfMemory Exception on the clone part.
I know, ussually, the OutOfMemory Exception will also be thrown when I specify a region out of bounds, but since the function is working like a charm on my machine I can't imagine that is the case. Besides that, the program doesn't break after a few tries, it breaks on all attempts to clone.
Doesn't matter if there is text (DrawString method) or not. It breaks on the Clone.
The images being processed are big, but not "huge" (6016 x 4000 pixels at most) but even with smaller images (3264 x 2448 pixels) the client will break.
Variables:
bmOriginal : original bitmap
processImage : original image (pictureBox) - bmOriginal is a bitmap copy of this
watermarkText : textbox for extra information below watermark
black and white : pictureboxes containing the watermark images
watermarkCombo : combobox for selecting automatic, white or black (automatic fails)
Code:
using (Graphics gWatermark = Graphics.FromImage(bmOriginal))
{
gWatermark.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
System.Drawing.SolidBrush drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black);
// position watermark - watermark should be 10% of the image height
int watermarkHeight = (int)(processImage.Image.Height * 0.1);
int watermarkPadding = (int)(watermarkHeight * 0.1); // not completely true, but asume watermark is square
Rectangle watermarkArea = new Rectangle(watermarkPadding, processImage.Image.Height - (watermarkPadding + (watermarkText.Text.Length == 0 ? 0 : watermarkPadding) + watermarkHeight), watermarkHeight, watermarkHeight);
// determine color watermark
bmWatermark = (Bitmap)black.Image;
if (watermarkCombo.SelectedIndex == 0)
{
using (Bitmap watermarkClone = bmOriginal.Clone(watermarkArea, bmOriginal.PixelFormat))
{
var pixels = Pixels(watermarkClone);
if (pixels.Average((Func<Color, decimal>)Intensity) < 110) // human eye adoption; normal threshold should be 128
{
bmWatermark = (Bitmap)white.Image;
drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White);
}
}
}
else if (watermarkCombo.SelectedIndex == 1)
{
bmWatermark = (Bitmap)white.Image;
drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White);
}
// draw the watermark
gWatermark.DrawImage(bmWatermark, watermarkArea.X, watermarkArea.Y, watermarkArea.Width, watermarkArea.Height);
// draw the text (if needed)
if (watermarkText.Text.Length > 0)
{
System.Drawing.Font drawFont = new System.Drawing.Font("Tahoma", (float)watermarkPadding);
gWatermark.DrawString(watermarkText.Text, drawFont, drawBrush, watermarkPadding, bmOriginal.Height - (watermarkPadding * 2));
}
}
bmOriginal.Save(System.IO.Path.Combine(diWatermarked.FullName, fileName), System.Drawing.Imaging.ImageFormat.Jpeg);
line of error: using (Bitmap watermarkClone = bmOriginal.Clone(watermarkArea, bmOriginal.PixelFormat))
Now the big question: how do I get rid of that OutOfMemory exception.. anyone an idea?
EDIT When I choose not to determine the color of the watermark automatically and just add a watermark (let's say the white one) the program functions normally. I've seen the stack trace in an error log (on the catch of the function I output the exception and -if any- inner exceptions).
I know a lot of the OOM exceptions using the Clone function occur when you specify a region out of bounds; but that is not the case here.
When I look at my memory when using the app in debug mode, I start at 5.36 Gb program started and a normalized 5.39 Gb (with a max spike of 5.42 Gb) when running the execution I mentioned, it's not guzzling memory like crazy.
The code I use the determine the average "color" (it's from someone on StackOverflow - I just copied that from some other answer, can't find the link though);
// functions used to determine watermark color
private static decimal ComponentAverage(decimal a, decimal b)
{
return Math.Min(a, b) + Math.Abs(a - b) / 2M;
}
private static decimal Intensity(Color color)
{
decimal result = color.A;
result = ComponentAverage(result, color.R);
result = ComponentAverage(result, color.G);
result = ComponentAverage(result, color.B);
return result;
}
private static IEnumerable<Color> Pixels(Bitmap bitmap)
{
for (int x = 0; x < bitmap.Width; x++)
for (int y = 0; y < bitmap.Height; y++)
yield return bitmap.GetPixel(x, y);
}
SOURCE There is a test project here: http://hotpepper.nu/oomtestapp.zip
Leon, I've changed your code you've uploaded to not lock any resources.
I've notices that if I keep your application open, I cannot delete the output folders because some files are in use. This usually means that you DID NOT release all file handles, basically it is always the last file.
I couldn't reproduce the out of memory issue on my computer before and after my changes, seems to be an issue with very big files maybe?
Ok anyways, I found that you use ImageBox to load the white and black resource and to load the image from disc. This is not needed at all, intead use the resources directly
Bitmap white = OomTestApp.Properties.Resources.white;
Bitmap black = OomTestApp.Properties.Resources.black;
Then to load an image from disc, simply use Bitmap.FromFile
I have added some lines to release your resources properly .Dispose where no using blocks are used.
And I also removed the Clone() call because it is absolutely not needed I think, because you are just computing the pixels of the original image and you do not draw something into that image at that point. So what was the need to have the image cloned?
Here is the full code (starting after you created the folders)
if (errors == 0)
{
this.Height = 323;
goButton.Enabled = false;
stopButton.Enabled = true;
Bitmap white = OomTestApp.Properties.Resources.white;
Bitmap black = OomTestApp.Properties.Resources.black;
Bitmap bmWatermark = black;
Bitmap processImage = null;
progressBar1.Maximum = filesToProcess.Count;
foreach (string handleFile in filesToProcess)
{
string fileName = System.IO.Path.GetFileName(handleFile);
fileNameLabel.Text = "File: " + System.IO.Path.GetFileName(handleFile);
try
{
// create backup if checked
if (diOriginal != null)
{
System.IO.File.Move(handleFile, System.IO.Path.Combine(diOriginal.FullName, fileName));
processImage = (Bitmap)Bitmap.FromFile(System.IO.Path.Combine(diOriginal.FullName, fileName));
}
else
{
processImage = (Bitmap)Bitmap.FromFile(handleFile);
}
double aspectRatio = (double)processImage.Width / (double)processImage.Height;
using (Graphics gWatermark = Graphics.FromImage(processImage))
{
gWatermark.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
System.Drawing.SolidBrush drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black);
// position watermark - watermark should be 10% of the image height
int watermarkHeight = (int)(processImage.Height * 0.1);
int watermarkPadding = (int)(watermarkHeight * 0.1); // not completely true, but asume watermark is square
// calculate rectangle. if there is extra text, add extra padding below
Rectangle watermarkArea = new Rectangle(watermarkPadding, processImage.Height - (watermarkPadding + (watermarkText.Text.Length == 0 ? 0 : watermarkPadding) + watermarkHeight), watermarkHeight, watermarkHeight);
// determine color watermark
bmWatermark = black;
if (watermarkCombo.SelectedIndex == 0)
{
/*using (Bitmap watermarkClone = processImage.Clone(watermarkArea, processImage.PixelFormat))
{*/
var pixels = Pixels(processImage);
if (pixels.Average((Func<Color, decimal>)Intensity) < 110) // human eye adoption; normal threshold should be 128
{
bmWatermark = white;
drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White);
}
//}
}
else if (watermarkCombo.SelectedIndex == 1)
{
bmWatermark = white;
drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White);
}
// draw the watermark
gWatermark.DrawImage(bmWatermark, watermarkArea.X, watermarkArea.Y, watermarkArea.Width, watermarkArea.Height);
// draw the text (if needed)
if (watermarkText.Text.Length > 0)
{
System.Drawing.Font drawFont = new System.Drawing.Font("Tahoma", (float)watermarkPadding);
gWatermark.DrawString(watermarkText.Text, drawFont, drawBrush, watermarkPadding, processImage.Height - (watermarkPadding * 2));
drawFont.Dispose();
}
// disposing resources
drawBrush.Dispose();
}
// save the watermarked file
processImage.Save(System.IO.Path.Combine(diWatermarked.FullName, fileName), System.Drawing.Imaging.ImageFormat.Jpeg);
// stop button pressed?
Application.DoEvents();
if (stopProcess) break;
// update exection progress
progressBar1.Value++;
percentLabel.Text = ((int)((progressBar1.Value * 100) / filesToProcess.Count)).ToString() + "%";
fileCountLabel.Text = "File " + progressBar1.Value.ToString() + "/" + filesToProcess.Count.ToString();
}
catch (Exception ex)
{
try
{
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(System.IO.Path.Combine(folderText.Text, "errorlog.txt"), true))
{
sw.WriteLine("File: " + fileName);
while (ex != null)
{
sw.WriteLine("Message: " + ex.Message);
sw.WriteLine(ex.StackTrace);
sw.WriteLine(ex.Source);
ex = ex.InnerException;
}
sw.WriteLine();
}
}
catch
{
// nothing to do - it already failed
}
errors++;
}
finally
{
if (processImage != null) processImage.Dispose();
}
}
// dispose resources
white.Dispose();
black.Dispose();
bmWatermark.Dispose();
if (!stopProcess)
{
// set status to complete
fileCountLabel.Text = "File " + filesToProcess.Count.ToString() + "/" + filesToProcess.Count.ToString();
percentLabel.Text = "100%";
fileNameLabel.Text = "Completed...";
}
else
{
fileNameLabel.Text = "Aborted...";
}
fileNameLabel.Text += errors.ToString() + " error(s) encountered";
// defaults to screen
progressBar1.Value = progressBar1.Maximum;
stopProcess = false;
goButton.Enabled = true;
stopButton.Enabled = false;
Well, I didn't read everything on the page, but I wonder if anyone mentioned the "stride" of the Bitmap? Basically, your bitmap has to be a multiple of 4 bytes.
I had this problem taking apart a grid to make tiles. The last tile would error because the Bitmap was not evenly divisible by 4.
The stride is the width of a single row of pixels (a scan line),
rounded up to a four-byte boundary. If the stride is positive, the
bitmap is top-down. If the stride is negative, the bitmap is
bottom-up.
https://softwarebydefault.com/2013/04/11/bitmap-color-balance/