I found some code on the web which grabs the current window and copies it into a bitmap. I've included the pertinent bit below. Currently it copies the client area, but I'd like to get the frame as well. Is there a way to get the handle of that? So I'd like to snapshot the entire window including maximise button, control button, etc.
// Capture snapshot of the form...
if (base.IsHandleCreated)
{
//
// Get DC of the form...
IntPtr srcDc = GetDC(Handle);
//
// Create bitmap to store image of form...
var bmp = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);
//
// Create a GDI+ context from the created bitmap...
using (Graphics g = Graphics.FromImage(bmp))
{
//
// Copy image of form into bitmap...
IntPtr bmpDc = g.GetHdc();
BitBlt(bmpDc, 0, 0, bmp.Width, bmp.Height, srcDc, 0, 0, 0x00CC0020 /* SRCCOPY */);
Just use the form's DrawToBitmap() method:
using (var bmp = new Bitmap(this.Width, this.Height)) {
this.DrawToBitmap(bmp, new Rectangle(Point.Empty, this.Size));
bmp.Save("c:/temp/test.png");
}
Graphics.CopyFromScreen() is another way, similar to what you're doing now. It actually copies the image from the screen rather than asking the form to draw itself into a bitmap. With the same disadvantage, the form needs to be visible.
Related
There are other similar questions, but they deal with only screenshotting the displayed application. My application is transparent, so I need to take a screenshot of both the displayed window and the background behind it.
This function correctly screenshots the app on my main monitor, but when it goes to the other monitor, the screenshots are the wrong shape and are completely black. If the window is partly on the monitor, then it still works, but as soon as the other monitor takes control of the window, I get black screenshots. How do I get the screenshots to render correctly?
Here is my function:
public void SaveSnapshot2(int count)
{
string imageName = "image" + count.ToString() + ".jpeg";
string basePath = Path.GetFullPath(Environment.CurrentDirectory);
string folderPath = Path.Combine(basePath, "Snapshots");
string fullPath = Path.Combine(folderPath, imageName);
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
System.Windows.Point relativeWindowPosition = App.Current.MainWindow.PointToScreen(new System.Windows.Point(0, 0));
System.Windows.Point relativeBottomRightWindowPosition = App.Current.MainWindow.PointToScreen(new System.Windows.Point(App.Current.MainWindow.Width, App.Current.MainWindow.Height));
System.Windows.Point actualWidthHeight = new System.Windows.Point((relativeBottomRightWindowPosition.X - relativeWindowPosition.X), (relativeBottomRightWindowPosition.Y - relativeWindowPosition.Y));
System.Drawing.Size convertedSize = new System.Drawing.Size((int)actualWidthHeight.X, (int)actualWidthHeight.Y);
Bitmap Screenshot = new Bitmap((int)actualWidthHeight.X, (int)actualWidthHeight.Y, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(Screenshot))
{
// Crops the screenshot
// The relativePoint is where the top left corner of the image is. This is correct.
g.CopyFromScreen((int)relativeWindowPosition.X, (int)relativeWindowPosition.Y, 0, 0, convertedSize); // or (0, 0, 0, 0, Screenshot.Size) to get the whole bitmap image
}
try
{
Screenshot.Save(fullPath, ImageFormat.Jpeg);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ", the image did not save.");
}
Screenshot.Dispose();
}
Coming up with names for the points was difficult. I hope they are clear enough.
Does the coordinate system change when a window is on a separate monitor? Am I going about this the right way?
I guess you have not declared DPI awareness in the application manifest. It's a common pitfall when you have multiple monitors of different DPI. If have not, most methods and functions related to screen coordinates will return incorrect values and thus the calculation based on those values will be screwed up.
To solve this, add the application manifest which declares DPI awareness. See Setting the default DPI awareness for a process. PerMonitorV2 is the key for Per-Monitor DPI.
I have an EncodedImage. I want to draw a FontImage over it to get another EncodedImage. It’s similar to a LayeredLayout (a layer over another layer), but in this case I need a new EncodedImage with the two images merged.
Thank you
Use a mutable image.
Image img = Image.create(encImage.getWidth(), encImage.getHeight(), 0);
Graphics g = img.getGraphics();
g.drawImage(encImage, 0, 0);
g.drawImage(fontImage.scaled(encImage.getWidth(), encImage.getHeight()).toImage(), 0, 0);
The code bellow will paint he component on the image graphics - but it will translate it - instead of fitting it to the size of the graphics.
int ratio = 2;
Image screenshotImage = Image.createImage(getWidth()* ratio,getHeight()* ratio);
c.revalidate();
c.setVisible(true);
c.paintComponent(screenshotImage.getGraphics(), true);
I cannot use the the image and just scale it because some of the content will be truncated.
Can the component paint itself on image graphics with specified size.
Many thanks
Use:
c.setX(0);
c.setY(0);
c.setWidth(img.getWidth());
c.setHeight(img.getHeight());
if(c instanceof Container) {
c.setShouldLayout(true);
c.layoutContainer();
}
c.remove();
c.paintComponent(myImage.getGraphics(), true);
// add c back to it's parent container and revalidate
If the component is currently on a form you would want to undo all of that by calling revalidate on the parent form afterwards.
I am trying to display a rounded image that I get straight from the Internet.
I used the code below to create a round mask, get the image from the Internet, then tried to either set the mask on the image or the label itself. None of these approaches worked. If I remove the mask, the image is displayed fine. If I keep the code to set the mask then all I see is an empty white circle.
I have the idea that if I apply the mask on the image itself, then it may not take effect because the image was not downloaded at the time the mask was applied.
But I don't seem to understand why calling setMask on the label is also not working.
// Create MASK
Image maskImage = Image.createImage(w, l);
Graphics g = maskImage.getGraphics();
g.setAntiAliased(true);
g.setColor(0x000000);
g.fillRect(0, 0, w, l);
g.setColor(0xffffff);
g.fillArc(0, 0, w, l, 0, 360);
Object mask = maskImage.createMask();
// GET IMAGE
com.cloudinary.Cloudinary cloudinary = new com.cloudinary.Cloudinary(ObjectUtils.asMap(
"cloud_name", "REMOVED",
"api_key", "REMOVED",
"api_secret", "REMOVED"));
// Disable private CDN URLs as this doesn't seem to work with free accounts
cloudinary.config.privateCdn = false;
Image placeholder = Image.createImage(150, 150);
EncodedImage encImage = EncodedImage.createFromImage(placeholder, false);
Image img2 = cloudinary.url()
.type("fetch") // Says we are fetching an image
.format("jpg") // We want it to be a jpg
.transformation(
new Transformation()
.radius("max").width(150).height(150).crop("thumb").gravity("faces").image(encImage, "http://upload.wikimedia.org/wikipedia/commons/4/46/Jennifer_Lawrence_at_the_83rd_Academy_Awards.jpg");
Label label = new Label(img2);
label.setMask(mask); // also tried to do img2.applyMask(mask); before passing img2
So I tried various things:
1) Removing the mask that was set through cloudinary - That did not work
2) applied the mask to the placeholder & encoded image (as expected these shouldnt affect the final version that is getting published)
3) This is what works! I am not sure if the issue is really with downloading the picture before or after applying the mask.. time can tell down the road
Label label = new Label();
img2.applyMask(mask); // If you remove this line , the image will no longer be displayed, I will only see a rounded white circle ! I am not sure what this is doing, it might be simply stalling the process until the image is downloaded? or maybe somehow calling repaint or revalidate
label.setIcon( img2.applyMask(mask));
Here is what worked for me if anyone else is having similar issues:
//CREATE MASK
Image maskImage = Image.createImage(w, l);
Graphics g = maskImage.getGraphics();
g.setAntiAliased(true);
g.setColor(0x000000);
g.fillRect(0, 0, w, l);
g.setColor(0xffffff);
g.fillArc(0, 0, w, l, 0, 360);
Object mask = maskImage.createMask();
//CONNECT TO CLOUDINARY
com.cloudinary.Cloudinary cloudinary = new com.cloudinary.Cloudinary(ObjectUtils.asMap(
"cloud_name", "REMOVED",
"api_key", "REMOVED",
"api_secret", "REMOVED"));
// Disable private CDN URLs as this doesn't seem to work with free accounts
cloudinary.config.privateCdn = false;
//CREATE IMAGE PLACEHOLDERS
Image placeholder = Image.createImage(w, l);
EncodedImage encImage = EncodedImage.createFromImage(placeholder, false);
//DOWNLOAD IMAGE
Image img2 = cloudinary.url()
.type("fetch") // Says we are fetching an image
.format("jpg") // We want it to be a jpg
.transformation(
new Transformation()
.crop("thumb").gravity("faces")
.image(encImage, url);
// Add the image to a label and place it on the form.
//GetCircleImage(img2);
Label label = new Label();
img2.applyMask(mask); // If you remove this line , the image will no longer be displayed, I will only see a rounded white circle ! I am not sure what this is doing, it might be simply stalling the process until the image is downloaded? or maybe somehow calling repaint or revalidate
label.setIcon( img2.applyMask(mask));
Shai, I seriously appreciate your time!! Thank you very much. Will have to dig more into it if it gives me any other problems later but it seems to consistently work for now.
The Cloudinary API returns a URLImage which doesn't work well with the Label.setMask() method because, technically, a URLImage is an animated image (it is a placeholder image until it finishes loading, and then "animates" to become the target image).
I have just released a new version of the cloudinary cn1lib which gives you a couple of options for working around this.
I have added two new image() methods. One that takes an ImageAdapter parameter that you can use to apply the mask to the image itself, before setting it as the icon for the label. Then you wouldn't use Label.setMask() at all.
See javadocs for this method here
The other method uses the new Async image loading APIs underneath to load the image asynchronously. The image you receive in the callback is a "real" image so you can use it with a mask normally.
See javadocs for this method here
We are looking at adding a soft warning to the Label.setMask() and setIcon() methods if you try to add an "animated" image and mask it so that it is more clear.
I think the making code you set to the label might be conflicting with the masking code you get from Cloudinary.
In my web project, I need dynamically render XAML frames to animated gif. It's working now - I'm rendering each frame to png with this code:
// Save current canvas transform
var transform = surface.LayoutTransform;
// reset current transform (in case it is scaled or rotated)
surface.LayoutTransform = null;
// Get the size of canvas
var size = new System.Windows.Size(surface.Width, surface.Height);
// Measure and arrange the surface
// VERY IMPORTANT
surface.Measure(size);
surface.Arrange(new Rect(size));
// Create a render bitmap and push the surface to it
var renderBitmap =
new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(surface);
Bitmap bmp;
// Create a file stream for saving image
using (var stream = new MemoryStream()) {
// Use png encoder for our data
var encoder = new PngBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(stream);
bmp = new Bitmap(stream);
}
return bmp;
and then creating animated gif with MagickImage.
But when I'm putting it on a web page (on svg canvas), background isn't transparent, but black.
How to make it transparent?
You have to ensure that you have the transparent flag set. You will also have to get the index of the transparent colour and ensure that is set as well and ensure that all transparent pixels index the transparent colour index (note the transparent pixel can have any RGB value).
I had a look at the MagickImage documentation and it gave me nothing on GIF. Then I looked at your code again and you are encoding a png not a gif. I found the gif info and well maybe you gave an intermediate step. The API does not have anything about GIF all I can do is give you the details on the gif data block so you can find the correct flags and settings yourself.
The stuff you have to change is in the GCE block before the start of every image data block.
Correct settings for transparent
options.delay = 2; //what ever you want Dont go under 2 as many gif renderers are limited to 50frames a second. Frame delay in 1/100 secs
options.transparentFlag = true; // This must be true for transparent
options.transparentIndex = transColour; //The 8 bit index of the transparent colour
// this must be set correctly for transparent to work.
options.dispose = 3; //use 3 or 2 if you want transparency;
Writing the GCE block
// the following is writing the GCE block that must be included before every image
// write writes an array or single byte to the stream
// shortData creates a Little-endian 16 bit short (high byte first) byte array
//
GCE_ID = 0xf9; // GCE block indentifier
GCE_size = 4; // block length
write([0x21 , GCE_ID , GCE_size]); // extension introducer
write(
0 + // bits 8:7 reserved
(options.dispose << 2) + // bits 5:4:3:2 disposal method
(options.transparentFlag?1:0)); // 1 transparency flag
write( shortData(options.delay) ); // delay x 1/100 sec
write( options.transparentIndex ); // transparent color index
write(0); // block terminator
// image block follows
Not the type of answer you are after but I don't think you are saving gifs. If you are than you should be able to find out what to set so that the correct GCE blocks are written to the gif file. Last note. Use a different colour index for the background colour than you use for the transparent index. I reserve the last two indexes of the global RGB lookup table for background and transparent.