I'm trying to create a level editor using Windows Forms for my monogame project and need to draw small pixel based images to a picture box with no quality loss when scaled. In monogame when I need to do this I can just set the draw type to PointClamp and then each pixel is drawn as is instead of being pixelated when zoomed; I was hoping for something like this via a picturebox. Right now it looks like this But I'd prefer a more crisp and clean image like this (The second is how it'll appear in monogame). I haven't uploaded any code for this, but just assume I grabbed an image from the filestream and used the bitmap constructor to scale it up (don't think that's relevent but I'll just put it out there).
Image croppedImage, image = tileMap.tileBox.Image;
var brush = new SolidBrush(Color.Black);
try { croppedImage = CropImage(image, tileMap.highlightedRect); } catch {
return; // If crop target is outside bounds of image then return
}
float scale = Math.Min(higlightedTileBox.Width / croppedImage.Width, higlightedTileBox.Height / image.Height);
var scaleWidth = (int)(higlightedTileBox.Width * scale);
var scaleHeight = (int)(higlightedTileBox.Height * scale);
try { higlightedTileBox.Image = new Bitmap(croppedImage, new Size(scaleWidth, scaleHeight)); } catch {
return; // Image couldn't be scaled or highlighted tileBox couldn't be set to desired image
}
CropImage:
private static Image CropImage(Bitmap img, Rectangle cropArea) {
return img.Clone(cropArea, img.PixelFormat);
}
private static Image CropImage(Image img, Rectangle cropArea) {
return CropImage(new Bitmap(img), cropArea);
}
The code above is my current method in it's entirety. tileMap is a form and tilebox is the picturebox within that form.image is the full spritesheet texture before being cropped to what the user has highlighted. After being cropped I attempt to set the current picturebox (highlightedTileBox's) image to a scaled up version of the cropped image.
So I got a solution by trying around a bit.
It looks like scaling images directly by size is using some sort of interpolation.
To try different interpolation modes supported by Winforms, I created a little demo.
As you can see, every label contains the name of the InterpolationMode and is followed by its resulting image. The original bitmap I used is the small one at the top.
From your question, it looks like you would like to achieve something like NearestNeighbour.
Following code scales bmp and the result is stored in bmp2. Try if that's what you want. Consider building a proper implementation if you're using this as solution (disposing unused bitmaps etc.).
I hope it helps.
Bitmap bmp = new Bitmap("test.bmp");
Bitmap bmp2;
Graphics g = Graphics.FromImage(bmp2=new Bitmap(bmp.Width * 2, bmp.Height * 2));
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(bmp, 0, 0, bmp.Width * 2, bmp.Height * 2);
g.Dispose();
Related
Problem:
I have a watermark that I want to print on an Image. The Image varies in size so sometimes the watermark is too big and sometimes it's too small. In order to fix this I calculate the size of the image and resize the watermark. However, after resizing the Image, black borders appear around its margins.
Code
I am on a Mac using .NET Core3.1 and I am using two NuGet packages that helps to draw images / bitmaps. One is System.Drawing.Common and the other one, because I am on macOS is, runtime.osx.10.10x64.CoreCompat.System.Drawing.
The code that I use to resize the watermark founded here:
Bitmap watermarkNew = new Bitmap(watermark, new Size(image.Width / 10 * 3, image.Height / 10 * 3));
I have to use / 10 * 3 because the Bitmap constructor doesn't accept floats values, so I cannot multiply by * 0.3.
Results:
watermark before watermark after
To superimpose an Image on another, it's preferable to use an unscaled Image than generate a new Bitmap based on the desired size beforehand.
▶ The two Image are meant to blend, thus the scaling of one of the Images, in this case the Watermark Image, should be performed while the Image to scale is painted over the other with a SourceOver operation.
This way, the internal GDI+ (well, the GDI+ replica here) functions have means to calculate the blending procedure correctly.
This also prevents the copy to show imperfect semi-transparent pixels (similar to a dark halo) generated when a smaller Image is created using the new Bitmap() method.
▶ Also, we need to be sure that all operations are performed on a 32BitArgb Bitmaps.
It's better to create a 32BitArgb copy of the destination Image and draw the watermark on this copy. This can also ensure a better result. GDI+ function work better on this kind of Images.
Here, the CopyToArgb32() method takes care of this aspect, also applying the DPI resolution of the original Image to the copy.
▶ Furthermore, this produces a distorted Image (unless that's the expected result, that is):
Bitmap watermarkNew = new Bitmap(watermark, new Size(image.Width / 10 * 3, image.Height / 10 * 3));
The watermark Image dimensions should be resized calculating a scale factor that is a desired fraction (a percentage or a fixed measure) or the destination Image.
For example, to occupy a maximum size equals to one third of the destination Bitmap minimum dimension.
In other words, if the destination Bitmap size is 1500x600 px, the watermark Bitmap will be scaled proportionally to have a maximum Height of 200px:
float scale = (Math.Min(original.Width, original.Height) * .33f) /
Math.Min(watermark.Width, watermark.Height);
SizeF watermarkSize = new SizeF(watermark.Width * scale, watermark.Height * scale);
To further improve the blending, the Watermark could be made less opaque (or, more transparent, as you want to see it).
This can be simply achieved using as ColorMatrix as shown here:
How to apply a fade transition effect to Images
All combined in a class object that exposes a Watermark([Bitmap], [Bitmap], [Imageformat]) static method.
In the sample code, the Watermark is scaled to 1/3 of the maximum dimension of destination image and centered (just a generic placement, since the position of the watermark is not specified):
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
public class BitmapOperations
{
public static Bitmap Watermark(Bitmap watermark, Bitmap original, ImageFormat format)
{
var units = GraphicsUnit.Pixel;
float scale = (Math.Max(original.Width, original.Height) * .33f) /
Math.Max(watermark.Width, watermark.Height);
var watermarkSize = new SizeF(watermark.Width * scale, watermark.Height * scale);
var watermarkBounds = CenterRectangleOnRectangle(
new RectangleF(PointF.Empty, watermarkSize), original.GetBounds(ref units));
var workImage = CopyToArgb32(original);
// Using the SetOpacity() extension method described in the linked question
// watermark = watermark.SetOpacity(.5f, 1.05f);
using (var g = Graphics.FromImage(workImage)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(watermark, watermarkBounds);
return workImage;
}
}
private static Bitmap CopyToArgb32(Bitmap source)
{
var bitmap = new Bitmap(source.Width, source.Height, PixelFormat.Format32bppArgb);
bitmap.SetResolution(source.HorizontalResolution, source.VerticalResolution);
using (var g = Graphics.FromImage(bitmap)) {
g.DrawImage(source, new Rectangle(0, 0, bitmap.Width, bitmap.Height),
new Rectangle(0, 0, bitmap.Width, bitmap.Height), GraphicsUnit.Pixel);
g.Flush();
}
return bitmap;
}
private static RectangleF CenterRectangleOnRectangle(RectangleF source, RectangleF destination)
{
source.Location = new PointF((destination.Width - source.Width) / 2,
(destination.Height - source.Height) / 2);
return source;
}
}
Results:
Applying an opacity level of 50% and small correction in gamma:
I have a Rectangle (rec) that contains the area in which a smaller image is contained within a larger image. I want to display this smaller image on a Picturebox. However, what I really am doing is using the smaller image as a picture detector for a larger image that is 333x324. So what I want to do is use the coordinates of the smaller image rectangle, and then draw to the Picturebox, starting from lefthand side of the rectangle, going outwards by 333 width and 324 height.
Currently my code works but it only displays the small image that was being used for detection purposes. I want it to display the smaller image + 300 width and + 300 height.
I fiddled with this code for hours and I must be doing something extremely basic wrong. If anyone can help me I would appreciate it so much!
My code for the class:
public static class Worker
{
public static void doWork(object myForm)
{
//infinitely search for maps
for (;;)
{
//match type signature for Threading
var myForm1 = (Form1)myForm;
//capture screen
Bitmap currentBitmap = new Bitmap(CaptureScreen.capture());
//detect map
Detector detector = new Detector();
Rectangle rec = detector.searchBitmap(currentBitmap, 0.1);
//if it actually found something
if(rec.Width != 0)
{
// Create the new bitmap and associated graphics object
Bitmap bmp = new Bitmap(rec.X, rec.Y);
Graphics g = Graphics.FromImage(bmp);
// Draw the specified section of the source bitmap to the new one
g.DrawImage(currentBitmap, 0,0, rec, GraphicsUnit.Pixel);
// send to the picture box &refresh;
myForm1.Invoke(new Action(() =>
{
myForm1.getPicturebox().Image = bmp;
myForm1.getPicturebox().Refresh();
myForm1.Update();
}));
// Clean up
g.Dispose();
bmp.Dispose();
}
//kill
currentBitmap.Dispose();
//do 10 times per second
System.Threading.Thread.Sleep(100);
}
}
}
If I understand correctly, the rec variable contains a rectangle with correct X and Y which identifies a rectangle with Width=333 and Height=324.
So inside the if statement, start by setting the desired size:
rec.Width = 333;
rec.Height = 324;
Then, note that the Bitmap constructor expects the width and height, so change
Bitmap bmp = new Bitmap(rec.X, rec.Y);
to
Bitmap bmp = new Bitmap(rec.Width, rec.Height);
and that's it - the rest of the code can stay the way it is now.
I don't have that much deep knowledge in c#. I am using TeeChart to plot chart. I am able to save image of chart in .jpg, .bmp e.t.c. What i need to do is before going to save I want to clip some part of image and then save rest as it is without changing pixel information or any other things.
I want to clip inside the block box part. the remaining graph should be as it is. In the same way i can able to clip end part graph also if i want. there should be no change in pixel or height of the image. as well the remaining image should cover the entire graph. Is it possible. Can any one help me please how to do it.
You can get the chart drawing area coordinates from tChart1.Chart.ChartRect. Here's an example clipping the chart legend into an image:
public Form1()
{
InitializeComponent();
InitializeChart();
}
private Bitmap chartBmp;
private void InitializeChart()
{
tChart1.Series.Add(new Steema.TeeChart.Styles.Bar()).FillSampleValues();
chartBmp = tChart1.Bitmap;
tChart1.GetLegendRect += tChart1_GetLegendRect;
}
void tChart1_GetLegendRect(object sender, Steema.TeeChart.GetLegendRectEventArgs e)
{
Rectangle cropRect = e.Rectangle;
Bitmap legendImg = new Bitmap(cropRect.Width, cropRect.Height);
using (Graphics g = Graphics.FromImage(legendImg))
{
g.DrawImage(chartBmp, new Rectangle(0, 0, legendImg.Width, legendImg.Height),
cropRect,
GraphicsUnit.Pixel);
}
legendImg.Save(#"c:\temp\legend.png");
}
var destBitmap = sourceBitmap.Clone(new Rect(0, 0, 100, 200), sourceBitmap.PixelFormat);
I have written a small application which will be used in my work environment for cropping images. The windows form (.NET 3.5) that contains the image has a transparent rectangle which I use for dragging over a section of an image and hitting a button to get me whatever was behind the rectangle.
Currently I am using the code below, this is causing me issues because the area that it is capturing is off by quite a few pixels, and I think it's something to do with my CopyFromScreen function.
//Pass in a rectangle
private void SnapshotImage(Rectangle rect)
{
Point ptPosition = new Point(rect.X, rect.Y);
Point ptRelativePosition;
//Get me the screen coordinates, so that I get the correct area
ptRelativePosition = PointToScreen(ptPosition);
//Create a new bitmap
Bitmap bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
//Sort out getting the image
Graphics g = Graphics.FromImage(bmp);
//Copy the image from screen
g.CopyFromScreen(this.Location.X + ptPosition.X, this.Location.Y + ptPosition.Y, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
//Change the image to be the selected image area
imageControl1.Image.ChangeImage(bmp);
}
If anyone can spot why when the image is redrawn its quite a bit out, I'd be eternally grateful at this point. Also, the ChangeImage function is fine - it works if I use a form as a select area but using a rectangle has jazzed things up a bit.
You've retrieved the relative position to the screen as ptRelativePosition, but you never actually use that - you add the rectangle's location to your form's location, which doesn't account for the form's border.
Here's that fixed, with a few optimizations:
// Pass in a rectangle
private void SnapshotImage(Rectangle rect)
{
// Get me the screen coordinates, so that I get the correct area
Point relativePosition = this.PointToScreen(rect.Location);
// Create a new bitmap
Bitmap bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
// Copy the image from screen
using(Graphics g = Graphics.FromImage(bmp)) {
g.CopyFromScreen(relativePosition, Point.Empty, bmp.Size);
}
// Change the image to be the selected image area
imageControl1.Image.ChangeImage(bmp);
}
Interestingly, this was because of the space between the main form and the control that the image was on and the toolbar at the top of the form separating the control and the top of the main form. To get around this I simply modified one line in capture screen to account for those pixels, as shown below:
g.CopyFromScreen(relativePosition.X + 2, relativePosition.Y+48, Point.Empty.X, Point.Empty.Y, bmp.Size);
Cheers
I'm creating an application (Windows Form) that allows the user to take a screenshot based on the locations they choose (drag to select area). I wanted to add a little "preview pane" thats zoomed in so the user can select the area they want more precisely (larger pixels). On a mousemove event i have a the following code...
private void falseDesktop_MouseMove(object sender, MouseEventArgs e)
{
zoomBox.Image = showZoomBox(e.Location);
zoomBox.Invalidate();
bmpCrop.Dispose();
}
private Image showZoomBox(Point curLocation)
{
Point start = new Point(curLocation.X - 50, curLocation.Y - 50);
Size size = new Size(100, 90);
Rectangle rect = new Rectangle(start, size);
Image selection = cropImage(falseDesktop.Image, rect);
return selection;
}
private static Bitmap bmpCrop;
private static Image cropImage(Image img, Rectangle cropArea)
{
if (cropArea.Width != 0 && cropArea.Height != 0)
{
Bitmap bmpImage = new Bitmap(img);
bmpCrop = bmpImage.Clone(cropArea, bmpImage.PixelFormat);
bmpImage.Dispose();
return (Image)(bmpCrop);
}
return null;
}
The line that fails and has the Out of Memory exception is:
bmpCrop = bmpImage.Clone(cropArea, bmpImage.PixelFormat);
Basically what this does is it takes a 100x90 rectangle around the mouse pointer and pulls that into the zoomBox, which is a picturebox control. However, in the process, i get an Out Of Memory error. What is it that i am doing incorrectly here?
Thanks for your assistance.
Out of memory in C# imaging, is usually sign of wrong rect or point - a bit of red herring. I bet start has negative X or Y when error happens or the Size.Hight + Y or Size.Width + X is bigger than Hight or width of the image.
MSDN explains that an OutOfMemoryException means
rect is outside of the source bitmap bounds
where rect is the first parameter to the Bitmap.Clone method.
So check that the cropArea parameter is not larger than your image.
In GDI+ an OutOfMemoryException does not really mean "out of memory"; the GDI+ error code OufOfMemory has been overloaded to mean different things. The reasons for this are historic and a well described by Hans Passant in another answer.
Use the Bitmap object like this:
using (Bitmap bmpImage = new Bitmap(img))
{
// Do something with the Bitmap object
}
you should check if curLocation.X is larger than 50, otherwise your rectangle will start in the negative area (and of course curLocation.Y)
If the zoom box goes off the edge of the desktop area, then when you try to crop, you are asking the system to make a new image that includes pixels outside of the video memory area. Make sure to limit your zoom box so that none of its extents is less than 0 or greater than the screen edges.
If you are creating new bitmaps over and over, you might need to call GC.Collect(); which will force C# to garbage collect