Everywhere I look online, I see people posting on how to successfully crop an image. However, I want to 'crop'/ clear a hole out of an image. I want to keep the original image, but crop out a rectangle
As you can see in the image above, I have "cropped" out the kittens face. I maintained the original image, but removed only part of it. I cannot figure out how to do that.
Assuming you want to replace the original pixel colors with transparency you run into a small problem: You can't draw or fill with transparency in GDI+.
But you can use Graphics.Clear(Color.Transparent).
To do that you restrict the region where the Graphics object will draw. Here we can use the simple cropping rectangle but you can clear more complex shapes using a GraphicsPath..
Example using a bitmap bmp:
using (Graphics g = Graphics.FromImage(bmp))
{
Rectangle crop = new Rectangle(222,222,55,55);
g.SetClip(crop);
g.Clear(Color.Transparent);
}
bmp.Save(somefilename, ImageFormat.Png);
Setting your Graphics object's CompositingMode property to CompositingMode.SourceCopy will allow your drawing operations to replace the alpha value instead of proportionally opacifying it:
public static void TestDrawTransparent()
{
//This code will, successfully, draw something transparent overwriting an opaque area.
//More precisely, it creates a 100*100 fully-opaque red square with a 50*50 semi-transparent center.
using(Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb))
{
using(Graphics g = Graphics.FromImage(bmp))
using(Brush opaqueRedBrush = new SolidBrush(Color.FromArgb(255, 255, 0, 0)))
using(Brush semiRedBrush = new SolidBrush(Color.FromArgb(128, 255, 0, 0)))
{
g.Clear(Color.Transparent);
Rectangle bigRect = new Rectangle(0, 0, 100, 100);
Rectangle smallRect = new Rectangle(25, 25, 50, 50);
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
g.FillRectangle(opaqueRedBrush, bigRect);
g.FillRectangle(semiRedBrush, smallRect);
}
bmp.Save(#"C:\FilePath\TestDrawTransparent.png", ImageFormat.Png);
}
}
In this code, I first draw a fully-opaque red square, then a semi-transparent red square "over" it. The result is a semi-transparent "hole" in the square:
And on a black background:
A zero-opacity brush works just as well, leaving a clear hole through the image (I checked).
With that in mind, you should be able to crop any shapes you want, simply by filling them with a zero-opacity brush.
Related
I have seen, that the drawing of two images is time consuming, so I want to reduce drawing these images as much as possible. I use pictureedit and have overriden the ondraw event.
Here I added the below part, but it ends up uin a black image. I do want to check, whether I need to draw at all. This check works. Then I draw the background and overlay it by another one and I want to store this as image and put it as background for my control.
If I do not redraw the background I just call:
g.Clear(Color.Transparent);
in my pictureEdit paint event.
Did I something wrong?
Best Regards,
Patrick
//Recalculate the background
if (redrawBackground)
{
g.FillRectangle(Brushes.White, 0, 0, backGroundImage.Width, backGroundImage.Height);
g.Clear(Color.White);
//this is drawn without aspect ration. The matrix is stored, since drawing images is expensive
GenerateTransformation(g, false);
g.DrawImageUnscaled(backGroundImage, new Rectangle(0, 0, backGroundImage.Width, backGroundImage.Height));
lastTransformation = g.Transform;
GenerateTransformation(g, true);
g.DrawImage(foreGroundImage, new Rectangle(0, 0, backGroundImage.Width, backGroundImage.Height));
lastTransformationAspect = g.Transform;
//Save bitmap as background
Bitmap bmp = new Bitmap(backGroundImage.Width, backGroundImage.Height, e.Graphics);
//Trick the background by a new image
pictureEdit.Image = bmp;
}
Your code (new Bitmap(backGroundImage.Width, backGroundImage.Height, e.Graphics);
) does not copy the image to the Bitmap; it only associates the Graphics object with the bitmap.
Try this:
Bitmap bmp = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bmp);
g.DrawSomething(); // This draws to the bitmap
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 facing a really perplexing problem..
I have a .Net 2.0 C# WinForms project.
I'm trying to stretch a bitmap onto a drawing area, but for some reason it is not stretched properly - I get alpha channel gradient on the right and bottom margins of my drawing area.
It took me quite a while to isolate this problem. I create a few lines of code that reproduce the problem (see code snippet and screenshot below).
Can anyone please shed some light over this matter?
Thanks in advance.
--
private void Form1_Paint( object sender, PaintEventArgs e )
{
// Create a black bitmap resource sized 10x10
Image resourceImg = new Bitmap( 10, 10 );
Graphics g = Graphics.FromImage( resourceImg );
g.FillRectangle( Brushes.Black, 0, 0, resourceImg.Width, resourceImg.Height );
Rectangle drawingArea = new Rectangle( 0, 0, 200, 200 ); // Set the size of the drawing area
e.Graphics.FillRectangle( Brushes.Aqua, drawingArea ); // Fill an aqua colored rectangle
e.Graphics.DrawImage( resourceImg, drawingArea ); // Stretch the resource image
// Expected result: The resource image should completely cover the aqua rectangle.
// Actual Result: The right and bottom edges become gradiently transparent (revealing the aqua rectangle under it)
}
The behavior has to do with how GDI+ handles edges. In this case, you're scaling a very small image over a large area, and you haven't told GDI+ how to handle the edge. If you use the ImageAttributes class and set the WrapMode appropriately, you can get around this issue.
For example:
private void Form1_Paint(object sender, PaintEventArgs e)
{
using (var resourceImg = new Bitmap(10, 10))
{
using (var g = Graphics.FromImage(resourceImg))
{
g.FillRectangle(Brushes.Black, 0, 0,
resourceImg.Width, resourceImg.Height);
}
var drawingArea = new Rectangle(0, 0, 200, 200);
e.Graphics.FillRectangle(Brushes.Aqua, drawingArea);
using (var attribs = new ImageAttributes())
{
attribs.SetWrapMode(WrapMode.TileFlipXY);
e.Graphics.DrawImage(resourceImg, drawingArea,
0, 0, resourceImg.Width, resourceImg.Height,
GraphicsUnit.Pixel, attribs);
}
}
}
The above code should produce an all black image. If you comment out the attribs.SetWrapMode(WrapMode.TileFlipXY); statement, you should see the blue gradient. With the wrap mode set, you're telling GDI+ to flip the image at the edges, so it will pick up more black and not fade things out at the edge when it scales the image.
I am currently painting a light blue, partly transparent overlay over owner-drawn objects to indicate certain state. It's OK but I thought that it would be even nicer if I could at some sort of glass effect to further establish the idea that the particular object has "something" overlaid over the top of it.
I thought that some glass streaks, for example, in addition to the blue transparency would lend a nice effect.
I've Googled around for GDI+ (and others) algorithms to do simple things painting like this but have come up empty. Links to any (fairly simple) algorithms in any language would be appreciated. I prefer .NET but can figure out the painting from pseudo-code on up.
Sorry, shoul've also specified that I need to target WinXP and using .NET version 2.0 - So unable to use WPF or Vista/Win7 goodies.
I've not done this myself but, have used codeproject source to render a sample...Try this:
http://www.codeproject.com/KB/GDI-plus/Image-Glass-Reflection.aspx
public static Image DrawReflection(Image _Image, Color _BackgroundColor, int _Reflectivity)
{
// Calculate the size of the new image
int height = (int)(_Image.Height + (_Image.Height * ((float)_Reflectivity / 255)));
Bitmap newImage = new Bitmap(_Image.Width, height, PixelFormat.Format24bppRgb);
newImage.SetResolution(_Image.HorizontalResolution, _Image.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(newImage))
{
// Initialize main graphics buffer
graphics.Clear(_BackgroundColor);
graphics.DrawImage(_Image, new Point(0, 0));
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
Rectangle destinationRectangle = new Rectangle(0, _Image.Size.Height,
_Image.Size.Width, _Image.Size.Height);
// Prepare the reflected image
int reflectionHeight = (_Image.Height * _Reflectivity) / 255;
Image reflectedImage = new Bitmap(_Image.Width, reflectionHeight);
// Draw just the reflection on a second graphics buffer
using (Graphics gReflection = Graphics.FromImage(reflectedImage))
{
gReflection.DrawImage(_Image,
new Rectangle(0, 0, reflectedImage.Width, reflectedImage.Height),
0, _Image.Height - reflectedImage.Height, reflectedImage.Width,
reflectedImage.Height, GraphicsUnit.Pixel);
}
reflectedImage.RotateFlip(RotateFlipType.RotateNoneFlipY);
Rectangle imageRectangle =
new Rectangle(destinationRectangle.X, destinationRectangle.Y,
destinationRectangle.Width,
(destinationRectangle.Height * _Reflectivity) / 255);
// Draw the image on the original graphics
graphics.DrawImage(reflectedImage, imageRectangle);
// Finish the reflection using a gradiend brush
LinearGradientBrush brush = new LinearGradientBrush(imageRectangle,
Color.FromArgb(255 - _Reflectivity, _BackgroundColor),
_BackgroundColor, 90, false);
graphics.FillRectangle(brush, imageRectangle);
}
return newImage;
}
I was actually able to achieve a basic glass effect by overlaying my image with a rectangle about one third the size of the image below that contains a gradient fill of white that starts at 25% opacity and goes to 75% opacity. This is single bit of painting produces a glassy "streak" that I was happy with. The same idea could be repeated a number of times with a variety of rect widths to produce several "streaks" that will give the illusion of a glass overlay.
You could try the Aero Glass function, if you are using Vista or Windows 7.
These might be helpful:
http://msdn.microsoft.com/en-us/library/aa969537%28VS.85%29.aspx#blurbehind
http://msdn.microsoft.com/en-us/library/ms748975.aspx
If I use TextRenderer.DrawText() using the Graphics object provided in the OnPaintBackground my text looks perfect. If I create my own Bitmap and use the Graphics object obtained from my Bitmap my text looks terrible. It looks like it is anti-aliasing the text using black, not the bitmap's background color. I can avoid this problem if I use Graphics.DrawString(), but this method has horrible kerning problems. What should I do? How can I get TextRenderer.DrawText() to anti-alias properly using the Bitmap's contents?
Looks terrible:
Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Red);
TextFormatFlags tf = TextFormatFlags.Left;
TextRenderer.DrawText(g, #"C:\Development\Testing\blag", font, clip, Color.White,
Color.Transparent, tf);
}
Looks good, but I want to render this onto a bitmap, NOT onto the control's surface:
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Red);
TextFormatFlags tf = TextFormatFlags.Left;
TextRenderer.DrawText(e.Graphics, #"C:\Development\Testing\blag", font, clip,
Color.White, Color.Transparent, tf);
}
What is the difference?
The answer is not to use TextRenderer. TextRenderer is a wrapper for the GDI (not GDI+) implementation of text rendering, which has lots of features, but doesn't interoperate well with in-memory DCs as you have discovered.
Use Graphics.DrawString & Graphics.MeasureString, but remember to pass it StringFormat.GenericTypographic to get accurate size and positioning.
The reason TextRenderer was introduced initially was that GDI+ didn't support all the complex scripts that GDI's Uniscribe engine did. Over time however GDI+ support for complex scripts has been expanded, and these days there aren't any good reasons left to use TextRenderer (it's not even the faster of the two anymore, in fact quite the opposite it appears).
Really, though, unless you are running into serious, measurable performance issues just use Graphics.DrawString.
I believe the problem is that the clear type text rendering doesn't work if the background is transparent. A few possible solutions.
Option 1. Fill the background of your bitmap with a color.
If you do this (as Tim Robinson did above in his code example by using g.Clear(Color.Red)) clear type will do the right thing. But your bitmap won't be completely transparent which might not be acceptable. If you use Graphics.MeasureText, you can fill just the rectangle around your text, if you like.
Option 2. Set TextRenderingHint = TextRenderingHintAntiAliasGridFit
This appears to turn off clear type. The text will be rendered at a lower quality than clear type on a background, but much better than the mess clear type on no background creates.
Option 3. Fill the text rectangle with white, draw the text and then find all the non-text pixels and put them back to transparent.
using (Bitmap bmp = new Bitmap(someWidth, someHeight))
{
using (Graphics g = Graphics.FromImage(bmp))
{
// figure out where our text will go
Point textPoint = new Point(someX, someY);
Size textSize = g.MeasureString(someText, someFont).ToSize();
Rectangle textRect = new Rectangle(textPoint, textSize);
// fill that rect with white
g.FillRectangle(Brushes.White, textRect);
// draw the text
g.DrawString(someText, someFont, Brushes.Black, textPoint);
// set any pure white pixels back to transparent
for (int x = textRect.Left; x <= textRect.Left + textRect.Width; x++)
{
for (int y = textRect.Top; y <= textRect.Top + textRect.Height; y++)
{
Color c = bmp.GetPixel(x, y);
if (c.A == 255 && c.R == 255 && c.G == 255 && c.B == 255)
{
bmp.SetPixel(x, y, Color.Transparent);
}
}
}
}
}
I know, it's a horrible hack, but it appears to work.
The answer is to use a BuffersGraphicsContext. This is the same system that .NET uses internally when you set the ControlStyles.OptimizedDoubleBuffer style on a control.
See http://msdn.microsoft.com/en-us/library/b367a457.aspx for more information about double buffering in .NET.
Another possible solution: Draw the whole thing to the screen, bitmap with text on top, and then write some code to 'screen capture' that portion of the screen. Not practical in all cases but you're right, DrawString creates weird text and DrawText onto a bitmap looks horrible.
If your bitmap is not the same size as your display area, it might just be a resizing issue, where .NET scales the bitmap to the display size and you get funny looking text.
Can you test with a bitmap created at the same size as your display area?
Can you post the smallest program that suffers from this problem? I can't reproduce it like this -- the antialiasing looks fine:
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
public class Program
{
public static void Main()
{
Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
using (Font font = new Font("Arial", 10, GraphicsUnit.Point))
using (Graphics g = Graphics.FromImage(bmp))
{
Rectangle clip = Rectangle.FromLTRB(0, 0, 100, 100);
g.Clear(Color.Red);
TextFormatFlags tf = TextFormatFlags.Left;
TextRenderer.DrawText(g, #"C:\Development\Testing\blag", font, clip, Color.White, Color.Transparent, tf);
}
Form form = new Form();
form.BackgroundImage = bmp;
Application.Run(form);
}
}