I have the following code:
private static void SplitTilesRecursive(Image original, int level)
{
int mapWidth = GetMapWidth(level);
int tilesOnSide = mapWidth/TileSize;
using (Image resized = ResizeImage(original, new Size(mapWidth, mapWidth)))
{
for (int x = 0; x < tilesOnSide; x++)
for (int y = 0; y < tilesOnSide; y++)
{
CropAndSaveTile(resized, x, y, level);
}
}
if (level > 0)
SplitTilesRecursive(original, level - 1);
}
private static void CropAndSaveTile(Image image, int x, int y, int level)
{
var info = (CropInfo) o;
var cropArea = new Rectangle(x * TileSize, y * TileSize, TileSize, TileSize);
using (var bmpImage = new Bitmap(image))
using (Bitmap bmpCrop = bmpImage.Clone(cropArea, bmpImage.PixelFormat))
{
string filename = String.Format(TileFilename, level, x, y);
// the Portable Network Graphics (PNG) encoder is used implicitly
bmpCrop.Save(Path.Combine(OutputDir, filename));
Console.WriteLine("Processed " + filename);
}
}
The method CropAndSaveTile takes a awhile, so I want to split that task off to a new thread using a thread pool. I've tried to accomplish this using Task.Factory.StartNew. The problem is that I need to pass those 4 parameters to the thread, so I had to create a class which I could cast to an object.
private class CropInfo
{
public CropInfo(Image image, int x, int y, int level)
{
Image = image;
X = x;
Y = y;
Level = level;
}
public Image Image { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Level { get; set; }
}
private static void SplitTilesRecursive(Image original, int level)
{
// ...
using (Image resized = ResizeImage(original, new Size(mapWidth, mapWidth)))
{
for (int x = 0; x < tilesOnSide; x++)
for (int y = 0; y < tilesOnSide; y++)
{
Task.Factory.StartNew(CropAndSaveTile, new CropInfo(resized, x, y, level));
}
}
// ...
}
private static void CropAndSaveTile(object o)
{
var info = (CropInfo) o;
// ...
}
This almost works. The problem is that new Bitmap(info.Image) throws an ArgumentException (Parameter is not valid). I've tested this without using the Task.Factory.StartNew and instead calling the method directly using CropAndSaveTile(new CropInfo(resized, x, y, level)); and it works fine. Something is happening between StartNew and and the thread runs. Could this be a synchronization issue caused by when SplitTilesRecursive ends the loop and resized is disposed of? And if not, how can I properly pass multiple parameters to a thread to be used as part of a thread pool?
Why do you have to create a class? You can just do this:
Task.Factory.StartNew(()=>CropandSaveTile(resized, y, y, level));
The language will create a class for you under the covers as a "closure".
Try to use local copies of x and y inside the loop:
for (int x = 0; x < tilesOnSide; x++)
for (int y = 0; y < tilesOnSide; y++)
{
int x1 = x;
int y1 = y;
Task.Factory.StartNew(() => CropAndSaveTile(resized, x1, y1, level));
}
This guarantees that each lambda sees a separate pair of x and y values.
Related
In my project, a background picture is fetched from cloud. But some picture is too light, and some are dark. So I need to detect the background picture's brightness and hence to decide whether to add a mask.
I searched the solution on Google and found most solution is for WPF and using Bitmap. But they are forbidden in UWP.
Like below code. When I run the UWP project, VS will report error:
System.PlatformNotSupportedException
HResult=0x80131539
Message=System.Drawing is not supported on this platform.
So how to detect it in UWP?
private static float GetBrightness(string url)
{
Bitmap bitmap = new Bitmap(url);
var colors = new List<Color>();
for (int x = 0; x < bitmap.Size.Width; x++)
{
for (int y = 0; y < bitmap.Size.Height; y++)
{
colors.Add(bitmap.GetPixel(x, y));
}
}
float imageBrightness = colors.Average(color => color.GetBrightness());
return imageBrightness;
}
This works:
private async Task<float> GetBrightness(string url)
{
WebRequest myrequest = WebRequest.Create(url);
WebResponse myresponse = myrequest.GetResponse();
var imgstream = myresponse.GetResponseStream();
// Try to create SoftwareBitmap
MemoryStream ms = new MemoryStream();
imgstream.CopyTo(ms);
var dec = await BitmapDecoder.CreateAsync(ms.AsRandomAccessStream());
var data = await dec.GetPixelDataAsync();
var bytes = data.DetachPixelData();
var colors = new List<Color>();
for (int x = 0; x < dec.PixelWidth; x++)
{
for (int y = 0; y < dec.PixelHeight; y++)
{
colors.Add(GetPixel(bytes, x, y, dec.PixelWidth, dec.PixelHeight));
}
}
float imageBrightness = colors.Average(color => color.GetBrightness());
return imageBrightness;
}
public Color GetPixel(byte[] pixels, int x, int y, uint width, uint height)
{
int i = x;
int j = y;
int k = (i * (int)width + j) * 3;
var r = pixels[k + 0];
var g = pixels[k + 1];
var b = pixels[k + 2];
return Color.FromArgb(0, r, g, b);
}
Source1, Source2
I'm trying to make this class for processing images, and I wanted it to have a Bitmap field, and a constructor that would allow me to give to path to the bitmap so other methods could use that bitmap. So I wanted it to be able to do this:
ImageProcessing image = new ImageProcessing("D:\\image.bmp");
int time_of_processing = image.grayscale();
image.SaveTo("D:\\image2.bmp");
And so this is my code:
public class ImageProcessing
{
Bitmap image;
public ImageProcessing(string path)
{
image = new Bitmap(path);
}
public int Grayscale()
{
Stopwatch time = new Stopwatch();
time.Start();
for (int x = 0; x < image.Width; x++)
{
for (int y = 0; y < image.Height; y++)
{
Color pixelColor = image.GetPixel(x, y);
int grayScale = (int)((pixelColor.R * 0.21) + (pixelColor.G * 0.72) + (pixelColor.B * 0.07));
Color newColor = Color.FromArgb(grayScale, grayScale, grayScale);
image.SetPixel(x, y, newColor);
}
}
time.Stop();
TimeSpan ts = time.Elapsed;
int milliseconds = ts.Milliseconds + (ts.Seconds * 1000);
return milliseconds;
}
public void SaveTo(string path)
{
image.Save(path);
}
}
And when i have this, Visual Studio tells me that image will always be null. How should the constructor look for it to work?
Add the namespace to the ImageProcessing class when you use it to make sure its always using the correct class. The following code (which is mostly just the original code) has been tested and works (it grayscales the image listed).
namespace WindowsFormsApplication10
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
string path1 = #"C:\work\test.bmp";
string path2 = #"C:\work\test2.bmp";
WindowsFormsApplication10.ImageProcessing image = new WindowsFormsApplication10.ImageProcessing(path1);
int time_of_processing = image.Grayscale();
image.SaveTo(path2);
}
}
public class ImageProcessing
{
Bitmap image;
public ImageProcessing(string path)
{
image = new Bitmap(path);
}
public int Grayscale()
{
Stopwatch time = new Stopwatch();
time.Start();
for (int x = 0; x < image.Width; x++)
{
for (int y = 0; y < image.Height; y++)
{
Color pixelColor = image.GetPixel(x, y);
int grayScale = (int)((pixelColor.R * 0.21) + (pixelColor.G * 0.72) + (pixelColor.B * 0.07));
Color newColor = Color.FromArgb(grayScale, grayScale, grayScale);
image.SetPixel(x, y, newColor);
}
}
time.Stop();
TimeSpan ts = time.Elapsed;
int milliseconds = ts.Milliseconds + (ts.Seconds * 1000);
return milliseconds;
}
public void SaveTo(string path)
{
image.Save(path);
}
}
}
From what i have read and found about the writablebitmap am i not able to change it in a different thread then the one that created it. But i should be able to created it on a diffrent thread then the UI thread, then freze it and parse it to the UI thread for display. Example: Thread cannot access the object
However when i do this, do it tell me "The calling thread cannot access this object because a different thread owns it." who is this, when i have frozen the bitmap?
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Timer imageTimer = new Timer(41);
imageTimer.Elapsed += async (sender, args) =>
{
ImageSource image = await GetImage((int)Math.Ceiling(Image.Width), (int)Math.Ceiling(Image.Height));
DispatcherHelper.CheckBeginInvokeOnUI(() => {
Image.Source = image;
});
};
imageTimer.Start();
}
public Task<WriteableBitmap> GetImage(int width, int height)
{
return Task.Factory.StartNew(() =>
{
WriteableBitmap bitmap = BitmapFactory.New(width, height);
using (bitmap.GetBitmapContext())
{
bitmap.Clear(System.Windows.Media.Colors.AliceBlue);
Random rnd = new Random();
Color[] Colors = new Color[100];
for (int i = 0; i <= 99; i++)
{
Colors[i] = Color.FromRgb((byte)rnd.Next(0, 255), (byte)rnd.Next(0, 255),
(byte)rnd.Next(0, 255));
}
double size = width / 50;
for (int i = 0; i <= 49; i++)
{
int from = (int)Math.Ceiling(size * (double)i);
int to = (int)Math.Ceiling(size * (double)(i + 1)) - 1;
var color = Colors[i];
bitmap.DrawFilledRectangle(from, 0, to, height, color);
}
}
bitmap.Freeze();
return bitmap;
});
}
}
public static class KasperWriteableBitMapExtentions
{
public static void DrawFilledRectangle(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, Color color)
{
// Use refs for faster access (really important!) speeds up a lot!
int w = bmp.PixelWidth;
int h = bmp.PixelHeight;
// Check boundaries
if (x1 < 0) { x1 = 0; }
if (y1 < 0) { y1 = 0; }
if (x2 < 0) { x2 = 0; }
if (y2 < 0) { y2 = 0; }
if (x1 > w) { x1 = w; }
if (y1 > h) { y1 = h; }
if (x2 > w) { x2 = w; }
if (y2 > h) { y2 = h; }
for (int y = y1; y < y2; y++)
{
for (int x = x1; x <= x2; x++)
{
bmp.SetPixel(x, y, color);
}
}
}
}
I'm having some trouble reading back pixel values from a Bitmap that I'm generating. I first generate a bitmap named maskBitmap in my class using this code:
void generateMaskBitmap()
{
if (inputBitmap != null)
{
Bitmap tempBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(tempBitmap))
{
Brush brush = Brushes.Black;
for (int y = 0; y < tempBitmap.Height; y += circleSpacing)
{
for (int x = 0; x < tempBitmap.Width; x += circleSpacing)
{
g.FillEllipse(brush, x, y, circleDiameter, circleDiameter);
}
}
g.Flush();
}
maskBitmap = (Bitmap)tempBitmap.Clone();
}
}
I then try to apply the mask to my original image using the following code:
void generateOutputBitmap()
{
if (inputBitmap != null && maskBitmap != null)
{
Bitmap tempBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height);
for (int y = 0; y < tempBitmap.Height; y++)
{
for (int x = 0; x < tempBitmap.Width; x++)
{
Color tempColor = maskBitmap.GetPixel(x, y);
if (tempColor == Color.Black)
{
tempBitmap.SetPixel(x, y, inputBitmap.GetPixel(x, y));
}
else
{
tempBitmap.SetPixel(x, y, Color.White);
}
}
}
outputBitmap = tempBitmap;
}
}
The mask bitmap is successfully generated and visible in a picture box, however the color values for every pixel when testing "tempColor" show empty (A = 0, R = 0, G = 0, B = 0). I am aware of the performance problems with getpixel/setpixel, but this is not an issue for this project. I'm also aware that "tempColor == Color.Black" is not a valid test, but that is just a place holder for my comparison code.
I am unable to reproduce your problem. I copy-and-pasted your code and made some modifications to make it work for me. I am able to confirm that tempColor is sometimes #FF000000.
I suspect you mixed up your bitmap references somewhere. Are you really sure you are never getting any value other than #00000000? Do your circleDiameter and circleSpacing have sensible values? And most importantly: are you absolutely sure that you are reading from the correct bitmap?
Here's my version of your code, which I know works:
using System;
using System.Drawing;
namespace Test
{
class Program
{
static void Main()
{
var bitmap = GenerateMaskBitmap(100, 100);
TestMaskBitmap(bitmap);
}
const int CircleDiameter = 10;
const int CircleSpacing = 10;
static Bitmap GenerateMaskBitmap(int width, int height)
{
Bitmap maskBitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(maskBitmap))
{
Brush brush = Brushes.Black;
for (int y = 0; y < maskBitmap.Height; y += CircleSpacing)
{
for (int x = 0; x < maskBitmap.Width; x += CircleSpacing)
{
g.FillEllipse(brush, x, y, CircleDiameter, CircleDiameter);
}
}
g.Flush();
}
return maskBitmap;
}
static void TestMaskBitmap(Bitmap maskBitmap)
{
for (int y = 0; y < maskBitmap.Height; y++)
{
for (int x = 0; x < maskBitmap.Width; x++)
{
Color tempColor = maskBitmap.GetPixel(x, y);
if (tempColor.ToArgb() != 0)
throw new Exception("It works!");
}
}
}
}
}
How do I use a background worker in this for loop?
int tmax = 10;
int xmax = newbitmap.Width;
int ymax = newbitmap.Height;
for (int t = 0; t <= tmax; t += 1)
{
for (int x = 0; x < xmax; x++)
{
for (int y = 0; y < ymax; y++)
{
if ((x / xmax) > (t / tmax))
{
Color originalco = newbitmap2.GetPixel(x, y);
outp.SetPixel(x, y, originalco);
}
else
{
Color originalco3 = newbitmap.GetPixel(x, y); ;
outp.SetPixel(x, y, originalco3);
}
}
pictureBox1.Image = outp;
}
}
This loop is a wipe transition from right to left, but it doesn't display the transition.
That because the backgroundWorker works in a different thread. You can use backgroundworker.ReportProgress(0, outp)
So:
You need to register to the event BackgroundWorker.ProgressChanged from the events window in Vistual Studio, or with this line:
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
The method:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var outp = (Bitmap)e.UserState;
prictureBox.Image = outp;
}
Your code sould be then:
int tmax = 10;
int xmax = newbitmap.Width;
int ymax = newbitmap.Height;
for (int t = 0; t <= tmax; t += 1)
{
for (int x = 0; x < xmax; x++)
{
for (int y = 0; y < ymax; y++)
{
if ((x / xmax) > (t / tmax))
{
Color originalco = newbitmap2.GetPixel(x, y);
outp.SetPixel(x, y, originalco);
}
else
{
Color originalco3 = newbitmap.GetPixel(x, y); ;
outp.SetPixel(x, y, originalco3);
}
}
backgroundWorker1.ReportProgress(t, outp);
}
}
First, you should use direct pixel manipulation as described here: http://msdn.microsoft.com/en-us/library/5ey6h79d.aspx
Then, you should use an array as lookup for all your threads which line has already been drawn and which hasn't. The threads look for a new line in this array and then draw it. But remember to lock the lookup array!
Your Do work method would look something like this: -
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
Bitmap newbitmap = (Bitmap)e.Argument;
int tmax = 10;
int xmax = newbitmap.Width;
int ymax = newbitmap.Height;
for (int t = 0; t <= tmax; t += 1)
{
for (int x = 0; x < xmax; x++)
{
for (int y = 0; y < ymax; y++)
{
if ((x / xmax) > (t / tmax))
{
Color originalco = newbitmap2.GetPixel(x, y);
outp.SetPixel(x, y, originalco);
}
else
{
Color originalco3 = newbitmap.GetPixel(x, y); ;
outp.SetPixel(x, y, originalco3);
}
}
pictureBox1.Image = outp;
}
}
bgWorker.ReportProgress(0,outp);
}
Then when your worker reports progress, it would raise the following event where you can safely update the UI:
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//UPDATE YOUR UI HERE
}
You can use the ReportProgress method of Background Worker to update the UI.
Read more