I am trying to make a screen sharing application. As a first step I am trying to share the host's screen using screenshots.
In order to connect the host with the client, I am using SignalR. After the authentication is done, I am starting a timer for the host that will take a screenshot when it elapses. The screenshot code is as follows:
public static byte[] TakeScreenshot(int width = 0, int height = 0)
{
var bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height,
PixelFormat.Format32bppArgb);
// Create a graphics object from the bitmap.
var gfxScreenshot = Graphics.FromImage(bmp);
// Take the screenshot from the upper left corner to the right bottom corner.
gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X,
Screen.PrimaryScreen.Bounds.Y,
0,
0,
Screen.PrimaryScreen.Bounds.Size,
CopyPixelOperation.SourceCopy);
if (width != 0 && height != 0)
bmp = new Bitmap(bmp, new Size(width, height));
using (var stream = new MemoryStream())
{
bmp.Save(stream, ImageFormat.Jpeg);
return stream.ToArray();
}
}
The timer's elapsed handler:
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var bytes = ImageHelper.TakeScreenshot();
string imageString = Convert.ToBase64String(bytes);
Communicator.Instance.Produce(new BroadcastDataComm() { DataType = DataType.Image, Data = imageString }, _connectedHost);
}
The screenshot will be received as byte array then converted to Base64String in order to broadcast it through SignalR. When the authentication is done I am creating a new WPF window (in the parent Xaml.cs class) and show it, then, all the clients authenticated to the host will receive the produced message and send the base64string to the new window:
ScreenSharingWindow _window = new ScreenSharingWindow();
private void AuthenticateSuccess(string id)
{
ConnectionId = $"Connected to: {id}";
_connectedHost = id;
Dispatcher.Invoke(() => _window.Show());
_timer.Start();
}
private void ScreenshotReceived(BroadcastDataComm ss)
{
if (!(ss.Data is string imgString))
return;
Dispatcher.Invoke(()=> _window.ImageData = imgString);
}
Finally, I am creating a Bitmap object from the string using those 2 properties in the new window and using a converter on that object so I can bind the XAML's Image control to it:
private Bitmap _image;
public Bitmap Image
{
get => _image;
set
{
_image = value;
NotifyPropertyChanged();
Image.Save(#"C:\Users\roudy\Desktop\test.jpeg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
private string _imageData;
public string ImageData
{
get => _imageData;
set
{
_imageData = value;
NotifyPropertyChanged();
var ms = new MemoryStream(Convert.FromBase64String(ImageData));
Image = new Bitmap(ms);
}
}
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Bitmap bmp)
return ImageSourceFromBitmap(bmp);
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteObject([In] IntPtr hObject);
public BitmapSource ImageSourceFromBitmap(Bitmap bmp)
{
var handle = bmp.GetHbitmap();
try
{
var ret = Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
return ret;
}
finally { DeleteObject(handle); }
}
}
I have added Image.Save(...) in the property's setter to make sure that the image is received as expected and it's working properly, and I can see the image correctly updating when I check it's thumbnail in the desktop. But for some reason, it just refuses to show in the WPF control. I've tried to create a bitmap from file and it worked fine so the binding and converter is correct.
Why won't the Image show on the control ?
Thank you.
EDIT: In order to check if the Binding is correct, I just created the bitmap from the new window's constructor and commented out the part where the parent window was setting the property. The new handler in the parent window would be:
private void ScreenshotReceived(BroadcastDataComm ss)
{
if (!(ss.Data is string imgString))
return;
//Dispatcher.Invoke(()=> _window.ImageData = imgString);
}
and the new window's constructor:
public ScreenSharingWindow()
{
InitializeComponent();
DataContext = this;
Image = new Bitmap(#"C:\Users\roudy\Desktop\SaveIcon.png");
}
Note: When I try to load the bitmap from file from within "ImageData" property it also doesn't work so it seems like it's a threading issue, when it's changed from the parent window (through SignalR handler) it doesn't show.
The problem was from SignalR. I am running 2 instances of the same project, the screenshot was being sent to the host instead of the client and the client needed it to create a new window with it, which results in the image being blank. Thank you #Clemens for making it easier to debug without needing converters and more complications.
Related
When my form opens it uses the file path that is provided from another form and displays the images, and when I close the form I want it to delete all the images.
But when I close it keeps stating the file is being used, is there a better way of doing it please?
public frmMark(string strImagePath)
{
InitializeComponent();
pbImage1.Image = new Bitmap(strImagePath + "1.jpg");
pbImage2.Image = new Bitmap(strImagePath + "2.jpg");
pbImage3.Image = new Bitmap(strImagePath + "3.jpg");
}
private void frmMark_FormClosing(object sender, FormClosingEventArgs e)
{
File.Delete(deletepath + "1.jpg");
}
Change this:
pbImage1.Image = new Bitmap(strImagePath + "1.jpg");
to this:
pbImage1.ImageLocation = Path.Combine(strImagePath, "1.jpg");
and for the others and your issue should go away because the files will not be locked, so you don't have to worry about unlocking them.
Perhaps create a language extension which clones the original image, load the image then deletes the physical file.
public static class PictureBoxExtensions
{
public static void LoadClone(this PictureBox pictureBox, string fileName)
{
var imageOriginal = Image.FromFile(fileName);
var imageClone = new Bitmap(imageOriginal.Width, imageOriginal.Height);
Graphics gr = Graphics.FromImage(imageClone);
gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
gr.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighSpeed;
gr.DrawImage(imageOriginal, 0, 0, imageOriginal.Width, imageOriginal.Height);
gr.Dispose();
imageOriginal.Dispose();
pictureBox.Image = imageClone; // assign the clone to picture box
File.Delete(fileName); // remove original
}
}
Or if not always needing to remove an image add another parameter,in this case remove where the default is to remove which of course you can set the default to false.
public static class PictureBoxExtensions
{
public static void LoadClone(this PictureBox pictureBox, string fileName, bool remove = true)
{
var imageOriginal = Image.FromFile(fileName);
var imageClone = new Bitmap(imageOriginal.Width, imageOriginal.Height);
Graphics gr = Graphics.FromImage(imageClone);
gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
gr.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighSpeed;
gr.DrawImage(imageOriginal, 0, 0, imageOriginal.Width, imageOriginal.Height);
gr.Dispose();
imageOriginal.Dispose();
pictureBox.Image = imageClone; // assign the clone to picture box
if (remove)
{
File.Delete(fileName); // remove original
}
}
}
Usage
public frmMark(string strImagePath)
{
InitializeComponent();
pbImage1.LoadClone(strImagePath + "1.jpg");
pbImage2.LoadClone(strImagePath + "2.jpg");
pbImage2.LoadClone(strImagePath + "3.jpg");
}
I'm trying to change an image on runtime but it's not working.
I have a user control that when you click on imagebtn it's opening a new window with a list of images.
the next step is to take the image selected from the list, close the new window, and put the image on the imagebtn.
the user control still opens in the background.
this is my code.
NewWindow:
private string myData;
public ImagesWindow()
{
InitializeComponent();
InitialImageList();
}
private async void InitialImageList()
{
//add try catch
string get = await HttpRequests.GetRequest(URLImages);
allJsonCategory = JsonConvert.DeserializeObject<List<ImageArray>>(get);
Console.WriteLine(get);
ImageBoxList.ItemsSource = images;
foreach (var item in allJsonCategory)
{
images.Add(item);
}
}
private void ImageBoxList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selectedImage = (ImageArray)ImageBoxList.SelectedItem;
myData = selectedImage.full_path;
Console.WriteLine("you clicked on: " + selectedImage.name);
ProductsCategory pro = new ProductsCategory();
pro.imagePath = myData;
this.Close();
}
my usercontrol(in mainWindow):
public void setImage(string imagePath)
{
if (!imageURL.Equals(""))
{
Image imageBtn = new Image();
var imgUrl = new Uri(imagePath);
var imageData = new WebClient().DownloadData(imgUrl);
// or you can download it Async won't block your UI
// var imageData = await new WebClient().DownloadDataTaskAsync(imgUrl);
var bitmapImage = new BitmapImage { CacheOption = BitmapCacheOption.OnLoad };
bitmapImage.BeginInit();
bitmapImage.StreamSource = new MemoryStream(imageData);
bitmapImage.EndInit();
imageBtn.Source = bitmapImage;
//this.imageBtn.InvalidateVisual();
}
}
XAML of the image:
where is my mistake?
thank you all :)
I have a gird view of my objects "Sessions". On user selection, every Session's Image gets display in an Image on my GUI. I have this routine where, on user SelectedSession, it Deletes any existing image, Takes a new Image frameSource, Saves it locally and then sets the private member variable ImagePath as its path:
public Session SelectedSession { get { return selectedSession; } set { SetValue(ref selectedSession, value); } }
public BitmapSource SessionImage { get { return sessionImage; } private set { SetValue(ref sessionImage, value); } }
//..
public void TakeSessionImage(BitmapSource frameSource)
{
if (SelectedSession != null)
{
if (File.Exists(SelectedSession.ImagePath))
File.Delete(SelectedSession.ImagePath); // delete old - works
SelectedSession.ImagePath = FileStructure.CurrentSessionPath + "\\" + SelectedSession.Name + ".png"; // set ImagePath - works - Technically it does not change. I kept it if I needed later to add anything to the file name like GUID or whatever
ImageIO.RotateAndSaveImage(SelectedSession.ImagePath, (WriteableBitmap)frameSource, -270); // save new image - works
SessionImage = SelectedSession.LoadImageFromFile(); // binded the image to display
}
}
Binding in Xaml:
<Image x:Name="currentSessionImage" Source="{Binding SessionImage}"/>
In "Session.cs" class:
public BitmapImage LoadImageFromFile()
{
if (File.Exists(ImagePath)) // image path is correct
{
try
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri(ImagePath);
image.EndInit();
return image;
}
catch
{
return null;
}
}
return null;
}
This has been debugged and all statements are valid. SessionImageis my property that is "binded" to an Image on my GUI. However, a very weird behavior is happening:
It is deleting the old image file and I can see that in windows explorer that the file is gone.
It is saving the new one correctly
It is loading the new image from the correct path
But then, it only displays the very First Image I ever took.
Whatever new image I send. It always displays the same image I took the first time. Could anyone please check it for me? I validated the whole code and all values are logical. No syntax errors anywhere.
Edit:
protected virtual bool SetValue<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(storage, value))
{
storage = value;
OnPropertyChanged(propertyName);
return true;
}
return false;
}
//..
protected void OnPropertyChanged(string propertyName)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
}
}
WPF caches bitmaps that are loaded from URIs. To avoid that, load the BitmapImage directly from file instead:
using (var fileStream = new FileStream(ImagePath, FileMode.Open, FileAccess.Read))
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = fileStream;
image.EndInit();
return image;
}
I am taking bitmap images from camera and store them in a List< Bitmap>.
Within the same class I can set pictureBox images directly from the list like:
pictureBox1.Image = imagesToReturn[0];
where imagesToReturn is a List< Bitmap>.
The problem occurs when I call this method from a different class. ('invalid parameter' error is thrown).
First I return the list from the first object,
then copy it in the second object,
and then destruct the first object.
When I check the returned list I see they are returned correctly (Check the image below; [2] is intentionally null).
Do you guys have any idea what might be the problem or how can I solve this?
Thanks for your valuable time and help.
(edit: long story short, The list you see on the picture above cannot be loaded to pictureboxes because of the 'invalid parameter' error)
Here are some of the codes:
These are in the first class and work perfectly:
/*These are in the first class and works perfectly.*/
public static List<Bitmap> ImagesToReturn = new List<Bitmap>();
public static void updateThumbnails(Bitmap img, int* pictureBoxIndex)
{
switch (*pictureBoxIndex)
{
case 1:
ImagesToReturn[0] = img;
pictureBox2.Image = ImagesToReturn[0];//so that I know they are stored correctly.
pictureBox2.Refresh();
break;
case 2:
ImagesToReturn[1] = img;
pictureBox3.Image = ImagesToReturn[1];
pictureBox3.Refresh();
break;
case 3:
ImagesToReturn[2] = img;
pictureBox4.Image = ImagesToReturn[2];
pictureBox4.Refresh();
break;
case 4:
ImagesToReturn[3] = img;
pictureBox5.Image = ImagesToReturn[3];
pictureBox5.Refresh();
break;
default:
break;
}
*pictureBoxIndex = 0;
img.Dispose();
}
public List<Bitmap> returnCapturedImageList()
{
return ImagesToReturn;
}
//Done button. Terminates the processes.
private void button2_Click(object sender, EventArgs e)
{
keepRunning = false; // in order to stop straming
this.DialogResult = DialogResult.OK;
}
And these are at the second class:
public static List<Bitmap> returnedImages = new List<Bitmap>();
private void button1_Click(object sender, EventArgs e)
{
WindowsFormsApplication6.Form2 cameraModule = new WindowsFormsApplication6.Form2();
var result = cameraModule.ShowDialog();
if (result == DialogResult.OK)
{
returnedImages = cameraModule.returnCapturedImageList()
pictureBox1.Image = returnedImages[0]; //ERROR is thrown here!
pictureBox1.Refresh();
cameraModule.DialogResult = DialogResult.Cancel;
cameraModule.Close();
}
returnedImages.Clear();
}
The issue is solved thanks to the user6144226 and LarsTech. They actually pointed out the mistake.
ImagesToReturn[0] = img; /*not copying or cloning*/
ImagesToReturn[1] = img;
ImagesToReturn[2] = img;
ImagesToReturn[3] = img;
The statements above were not copying image to the list. It was pointing to the 'img'. In other words, when I disposed the 'img' with following command:
img.Dispose();
the "ImagesToReturn[n]" was pointing to the memory that has already been disposed.
The proper way to do it was as follows:
ImagesToReturn[0] = new Bitmap(img); /*copying*/
ImagesToReturn[1] = new Bitmap(img);
ImagesToReturn[2] = new Bitmap(img);
And that solved the issue.
In my windows 8 application(C#), I want to capture images (I done) and then save it to a Sqlite database. As Sqlite is not supporting BipmapImage, Uri types etc (I tried but giving exceptions of not supported storage..) How can I do this?
I simply want to save images in local database that I have captured using camera and then retrieve these images (set to binding). Please suggest me other options to achieve this.
I also tried to convert Uri into string and then saving this string into SQLite db and then again converting string to Uri and then making Bitmap images but I couldn't achieve this (is it a right approach ?).
If you can share me any sample please do it. I spent many hours in it but don't know where I am doing wrong!
Thanks Zauk
Although I do not prefer to save images in a database.
But if you want to save images in the database then one way of doing this is to convert your images to base64 string and then save the string in SQLite database.
public string ConvertToString(Image image)
{
// First Convert image to byte array.
byte[] byteArray = new byte[0];
using (MemoryStream stream = new MemoryStream())
{
image.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
stream.Close();
byteArray = stream.ToArray();
}
// Convert byte[] to Base64 String
string base64String = Convert.ToBase64String(byteArray);
return base64String;
}
You can save camera captured image into SQLitedatabase in this way..
public class CamActivity extends Activity {
byte[] byteArray;
private static final int CAMERA_REQUEST = 1888;
protected static final int TAKE_PHOTO_CODE = 0;
public ImageView imageView;
private DBAdapter db;
byte [] imgbyte;
EditText txtView ;
String name;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
db=new DBAdapter(CamActivity.this);
db.open();
this.imageView = (ImageView)this.findViewById(R.id.imageView1);
txtView=(EditText)findViewById(R.id.editText1);
Button B = (Button) this.findViewById(R.id.camera);
B.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
// cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,getImageUri());
startActivityForResult(cameraIntent,CAMERA_REQUEST );
}
});
Button save = (Button)findViewById(R.id.saving);
save.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
name=txtView.getText().toString();
try
{
db.insertImageDetails(byteArray,name);
}
catch (Exception e) {
e.printStackTrace();
}
//mySQLiteAdapter.close();
Toast.makeText(getApplicationContext(), "processing", Toast.LENGTH_SHORT).show();
Toast.makeText(getApplicationContext(), "image saved", Toast.LENGTH_SHORT).show();
}});
Button G = (Button) this.findViewById(R.id.get);
G.setOnClickListener(new View.OnClickListener() {
public void onClick(View v)
{
Intent intent= new Intent(CamActivity.this,SecondActivity.class);
startActivity(intent);
}
});
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//final byte[] byteArray;
if (requestCode == CAMERA_REQUEST) {
Bitmap photo = (Bitmap) data.getExtras().get("data");
//imageView.setImageBitmap(photo);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
photo.compress(Bitmap.CompressFormat.PNG, 100, stream);
byteArray = stream.toByteArray();
System.out.println(byteArray);
Toast.makeText(getApplicationContext(), byteArray.toString(), Toast.LENGTH_SHORT).show();
}
}
}