I've a PictureBox in my form and load a image in it.
I need this PictureBox to change the transparency (opacity, visibilit..etc), because I need the user to see the image behind this PictureBox better, so when he wants, he just drag the control slider and the image starts to turn invisible, step by step, until he finds its ok, lets say 50% transparency.
I added the control slider but, cant find the way to do the rest. I tried the pictureBox.Opacity, pictureBox.Transparency, nothing works.
In winforms you will have to modify the alpha of the PictureBox.Image.
To do that in a fast way use a ColorMatrix!
Here is an example:
The trackbar code:
Image original = null;
private void trackBar1_Scroll(object sender, EventArgs e)
{
if (original == null) original = (Bitmap) pictureBox1.Image.Clone();
pictureBox1.BackColor = Color.Transparent;
pictureBox1.Image = SetAlpha((Bitmap)original, trackBar1.Value);
}
To use a ColorMatrix we need this using clause:
using System.Drawing.Imaging;
Now for the SetAlpha function; note that it is basically a clone from the MS link..:
static Bitmap SetAlpha(Bitmap bmpIn, int alpha)
{
Bitmap bmpOut = new Bitmap(bmpIn.Width, bmpIn.Height);
float a = alpha / 255f;
Rectangle r = new Rectangle(0, 0, bmpIn.Width, bmpIn.Height);
float[][] matrixItems = {
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, a, 0},
new float[] {0, 0, 0, 0, 1}};
ColorMatrix colorMatrix = new ColorMatrix(matrixItems);
ImageAttributes imageAtt = new ImageAttributes();
imageAtt.SetColorMatrix( colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
using (Graphics g = Graphics.FromImage(bmpOut))
g.DrawImage(bmpIn, r, r.X, r.Y, r.Width, r.Height, GraphicsUnit.Pixel, imageAtt);
return bmpOut;
}
Note the the ColorMatrix expects its elements to be be scaling factors with 1 being the identity. The TrackBar.Value goes from 0-255, just like a Bitmap alpha channel..
Also note that the function creates a new Bitmap, which may lead to GDI leaking. Here the PictureBox takes care of it, it seems; at least testing it with the taskmanger ('Details' - turn on GDI-objects column!) shows no problems :-)
Final note: This will work if and only if the PictureBox is nested in the control 'behind' it! If it merely overlaps this will not work!! In my example it sits on a TabPage, which is a Container and anything you drop on it gets nested inside. It would work the same if I had dropped it onto a Panel. But PictureBoxes are no containers. So if you want another PictureBox to show up behind it, then you need code to create the nesting: pboxTop.Parent = pBoxBackground; pboxTop.Location = Point.Empty;
Related
I cannot seem to programmatically create a colored bitmap to display in a PictureBox. The bitmap saves normally as a file, but is faded at the edges when displayed in a PictureBox. Here is simplified code used to create and display the Bitmap (in actual code, the bitmap generation is completely separate from the form, so forcing the bitmap size to match the picturebox size isn't possible):
Bitmap Bmp = new Bitmap(4, 4, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
using (Graphics gfx = Graphics.FromImage(Bmp))
using (SolidBrush brush = new SolidBrush(Color.BlueViolet))
{
gfx.FillRectangle(brush, 0, 0, 4, 4);
}
Then set the Image value on a PictureBox to the generated Bitmap:
pictureBox1.Image = Bmp;
Here is the resulting bitmap displayed in a 300x300 picturebox:
How do I set the Image on the PictureBox so that it displays the colored bitmap properly (full solid)?
EDIT: I am restricted to generating smaller source bitmaps, so upscaling into a PictureBox is unavoidable. The problem appears when the generated source bitmap is 4px or 100px square, so I believe these are relevant cases.
EDIT: The PictureBox scaling should be set to stretch or zoom for the issue to manifest. In this example case the 4x4 source bitmap is stretched to 300x300.
EDIT: The basic problem is PictureBox's inability to upscale small bitmaps into large controls. This is confusing because the Bitmap upscales nicely into a PictureBox.Background image. Unless you have a magic bullet that will fix the image upscaling problem, I think it might be best to go for clear and simple workarounds in your answer.
You are generating a 4x4 bitmap and it's being stretched. Specify the size to match the picture box instead:
int width = pictureBox1.Width;
int height = pictureBox1.Height;
var Bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
using (Graphics gfx = Graphics.FromImage(Bmp))
using (var brush = new SolidBrush(Color.BlueViolet))
{
gfx.FillRectangle(brush, 0, 0, width, height);
}
pictureBox1.Image = Bmp;
You will need to turn anti-aliasing off. Also, since you are using one color for the whole picturebox, why not make the bitmap 1x1? If you need it 4x4, change the int the top of the example from 1 to 4.
int hw = 1;
Bitmap Bmp = new Bitmap(hw, hw,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
using (Graphics gfx = Graphics.FromImage(Bmp))
{
// Turn off anti-aliasing and draw an exact copy
gfx.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
gfx.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
using (SolidBrush brush = new SolidBrush(Color.BlueViolet))
{
gfx.FillRectangle(brush, 0, 0, hw, hw);
}
}
pictureBox1.Image = Bmp;
UPDATE
Since you are still having the same issue by setting the picturebox to the image, you will have to get the graphics object from the picturebox and draw directly on it.
The code is very similar.
using (Graphics gfx = Graphics.FromImage(pictureBox1.Image))
{
// Turn off anti-aliasing and draw an exact copy
gfx.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
gfx.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
using (SolidBrush brush = new SolidBrush(Color.BlueViolet))
{
gfx.FillRectangle(brush, 0, 0,
pictureBox11.Width - 1,
pictureBox11.Height - 1);
}
}
// Force the picturebox to redraw with the new image.
// You could also use pictureBox11.Refresh() to do the redraw.
pictureBox11.Invalidate();
I tried to test your code and the image were properly displayed.
But when i used this code:
Rectangle srcRect = New Rectangle(0, 0, Bmp.Width, Bmp.Height);
Rectangle dstRect = New Rectangle(0, 0, PictureBox1.Width, PictureBox1.Height);
g = PictureBox1.CreateGraphics;
g.DrawImage(Bmp, dstRect, srcRect, GraphicsUnit.Pixel);
g.Dispose();
I did get exactly your result. In order to fix it:
Rectangle srcRect = New Rectangle(0, 0, Bmp.Width - 1, Bmp.Height - 1);
Rectangle dstRect = New Rectangle(0, 0, PictureBox1.Width, PictureBox1.Height);
g = PictureBox1.CreateGraphics;
g.DrawImage(Bmp, dstRect, srcRect, GraphicsUnit.Pixel);
g.Dispose();
Edit:
So you have the bitmap and you want to stretch it. And the bitmap has ane solid color. Do this insted:
Color pixelColor = Bmp.GetPixel(0, 0);
PictureBox1.BackColor = pixelColor;
valter
This seems like it should be simple, but I can't seem to find any way to do it. I have a custom WinForms control that has an overridden paint method that does some custom drawing.
I have a Bitmap in memory, and all I want to do is paint over the whole thing with a HashBrush, but preserve the alpha channel, so that the transparent parts of the bitmap don't get painted.
The bitmap in memory is not a simple shape, so it will not be feasible to define it as a set of paths or anything.
EDIT: In response to showing the code, there is a lot of code in the paint routine, so I'm only including a relevant snippet, which is the method in question. This method gets called from the main paint override. It accepts a list of images which are black transparency masks and combines them into one, then it uses a ColorMatrix to change the color of the combined image it created, allowing it to be overlayed on top of the background. All I want to accomplish is being able to also paint hashmarks on top of it.
private void PaintSurface(PaintEventArgs e, Image imgParent, List<Image> surfImgs, Rectangle destRect, ToothSurfaceMaterial material)
{
using (Bitmap bmp = new Bitmap(imgParent.Width, imgParent.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb))
{
using (Graphics g = Graphics.FromImage(bmp))
{
foreach (Image img in surfImgs)
{
g.DrawImage(img, System.Drawing.Point.Empty);
}
}
ColorMatrix matrix = new ColorMatrix(
new float[][] {
new float[] { 0, 0, 0, 0, 0},
new float[] { 0, 0, 0, 0, 0},
new float[] { 0, 0, 0, 0, 0},
new float[] { 0, 0, 0, 0.7f, 0},
new float[] { material.R / 255.0f,
material.G / 255.0f,
material.B / 255.0f,
0, 1}
});
ImageAttributes imageAttr = new ImageAttributes();
imageAttr.SetColorMatrix(matrix);
Rectangle r = GetSizedRect(imgParent, destRect);
e.Graphics.DrawImage(bmp,
r,
0,
0,
bmp.Width,
bmp.Height,
GraphicsUnit.Pixel, imageAttr);
}
}
The solution I ended up using was the following method. First I combine the individual masks into one, then create a new Bitmap and paint the whole thing with the HatchBrush, finally iterate through the mask and set the alpha values on the newly generated bitmap based on the mask.
private Bitmap GenerateSurface(Image imgParent, List<Image> surfImgs, ToothSurfaceMaterial material)
{
Bitmap mask = new Bitmap(imgParent.Width, imgParent.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (Graphics g = Graphics.FromImage(mask))
{
foreach (Image img in surfImgs)
{
g.DrawImage(img, System.Drawing.Point.Empty);
}
}
Bitmap output = new Bitmap(mask.Width, mask.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (Graphics g = Graphics.FromImage(output))
{
if (material.HatchStyle != null)
{
HatchBrush hb = new HatchBrush((HatchStyle)material.HatchStyle, material.FgColor, material.BgColor);
g.FillRectangle(hb, new Rectangle(0, 0, output.Width, output.Height));
}
else
{
SolidBrush sb = new SolidBrush(material.FgColor);
g.FillRectangle(sb, new Rectangle(0, 0, output.Width, output.Height));
}
}
var rect = new Rectangle(0, 0, output.Width, output.Height);
var bitsMask = mask.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsOutput = output.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
int offset = 0;
for (int y = 0; y < mask.Height; y++)
{
byte* ptrMask = (byte*)bitsMask.Scan0 + y * bitsMask.Stride;
byte* ptrOutput = (byte*)bitsOutput.Scan0 + y * bitsOutput.Stride;
for (int x = 0; x < mask.Width; x++)
{
offset = 4 * x + 3;
ptrOutput[offset] = (byte)(ptrMask[offset] * 0.7);
}
}
}
mask.UnlockBits(bitsMask);
output.UnlockBits(bitsOutput);
return output;
}
I think you don't need any ColorMatrix which is overkill, you just need a ColorMap, here is the code which may not suit your requirement but should give you the idea. That's because I possibly don't understand your problem well, if you have any problem, just leave some comment and I'll try to improve the answer:
ImageAttributes imgA = new ImageAttributes();
ColorMap cm = new ColorMap();
cm.OldColor = Color.Black
cm.NewColor = Color.FromArgb((byte)(0.7*255), Color.Green);
imgA.SetRemapTable(new ColorMap[] {cm });
GraphicsUnit gu = GraphicsUnit.Pixel;
g.DrawImage(imageToDraw,new Point[]{Point.Empty,
new Point(backImage.Width/2,0),
new Point(0,backImage.Height/2)},
Rectangle.Round(imageToDraw.GetBounds(ref gu)),
GraphicsUnit.Pixel, imgA);
the new Point[] is an array of 3 Points used to locate the destination Rectangle.
The code above is used to Draw the imageToDraw on top of the backImage and convert and color of Black to the color Green with Opacity = 70%. That's what you want to fulfill your code.
UPDATE
This may be what you want, in fact your code doesn't show what you want, it just shows what you have which doesn't implement anything related to your problem now. I deduce this from your very first description in your question. The input is an image with background color (which will be made partially transparent later) being Black. Now the output you want is an image with all the non-Black region being painted with a HatchBrush. This output will then be processed to turn the Black background to a partially transparent background.
public void PaintHatchBrush(Bitmap input, HatchBrush brush){
using(Graphics g = Graphics.FromImage(input)){
g.Clip = GetForegroundRegion(input, Color.Black);
GraphicsUnit gu = GraphicsUnit.Pixel;
g.FillRectangle(brush, input.GetBounds(ref gu));
}
}
//This is implemented using `GetPixel` which is not fast, but it gives you the idea.
public Region GetForegroundRegion(Bitmap input, Color backColor){
GraphicsPath gp = new GraphicsPath();
Rectangle rect = Rectangle.Empty;
bool jumpedIn = false;
for (int i = 0; i < bm.Height; i++) {
for (int j = 0; j < bm.Width; j++) {
Color c = bm.GetPixel(j, i);
if (c != backColor&&!jumpedIn) {
rect = new Rectangle(j, i, 1, 1);
jumpedIn = true;
}
if (jumpedIn && (c == backColor || j == bm.Width - 1)) {
rect.Width = j - rect.Left;
gp.AddRectangle(rect);
jumpedIn = false;
}
}
}
return new Region(gp);
}
//Usage
HatchBrush brush = new HatchBrush(HatchStyle.Percent30, Color.Green, Color.Yellow);
PaintHatchBrush(yourImage, brush);
//then yourImage will have HatchBrush painted on the surface leaving the Black background intact.
//This image will be used in the next process to turn the Black background into 70%
//opacity background as you did using ColorMatrix (or more simply using ColorMap as I posted previously)
First of all, I'd like to suggest that it is not a duplicate of THIS question. At least that's my opinion :)
What I want to achieve is a series of frames to "fade" animation.
I choose two PNG files (let' say they are the same size), for example:
Picture 1
Picture 2
I want to "simulate" merging them like layers in graphic editor. I put Pic1 on the top with opacity 255, Pic2 below with opacity 0, so at first I see only Pic1. Then I change their opacity, like this:
Pic1-200, Pic2-150
Pic1-150, Pic2-200
Pic1-100, Pic2-230
Is there any simple way for it?
In a winforms app this can be done pretty easily. Create a user control with a few properties:
public Image FromImage { get; set; }
public Image ToImage { get; set; }
private float opacity = 1;
Now override OnPaint
protected override void OnPaint(PaintEventArgs e)
{
if (FromImage != null && ToImage != null)
{
ColorMatrix matrix1 = new ColorMatrix();
matrix1.Matrix33 = opacity;
ImageAttributes attributes1 = new ImageAttributes();
attributes1.SetColorMatrix(matrix1, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
ColorMatrix matrix2 = new ColorMatrix();
matrix2.Matrix33 = 1 - opacity;
ImageAttributes attributes2 = new ImageAttributes();
attributes2.SetColorMatrix(matrix2, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
e.Graphics.DrawImage(FromImage, new Rectangle(0, 0, this.Width, this.Height), 0, 0, this.Width,
this.Height, GraphicsUnit.Pixel, attributes1);
e.Graphics.DrawImage(ToImage, new Rectangle(0, 0, this.Width, this.Height), 0, 0, this.Width,
this.Height, GraphicsUnit.Pixel, attributes2);
}
base.OnPaint(e);
}
Now drop a timer onto the control, set its to enabled with an elapsed time of something like 100ms. Handle the tick event:
private void timer_Tick(object sender, EventArgs e)
{
if(opacity == 0)
{
this.timer.Stop();
return;
}
this.opacity -= 0.01f;
this.Invalidate();
}
et voila. However, there's one thing to be aware of. This makes quite a flickery transition, which can be alieviated somewhat with this line in the control's constructor:
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint,true);
Update based on Edit: You could turn this into a utility that takes 2 images and, using much the same code, outputs each step to a new image. Somthing like:
public class ImageUtility
{
private Image image1;
private Image image2;
public ImageUtility(Image image1, Image image2)
{
this.image1 = image1;
this.image2 = image2;
}
public void SaveTransitions(int numSteps, string outDir)
{
var opacityChange = 1.0f/(float) numSteps;
for(float opacity = 1,i=0;opacity>0;opacity-=opacityChange,i++)
{
using(var image = new Bitmap(image1.Width,image2.Width))
{
Graphics g = Graphics.FromImage(image);
ColorMatrix matrix1 = new ColorMatrix();
matrix1.Matrix33 = opacity;
ImageAttributes attributes1 = new ImageAttributes();
attributes1.SetColorMatrix(matrix1, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
ColorMatrix matrix2 = new ColorMatrix();
matrix2.Matrix33 = 1 - opacity;
ImageAttributes attributes2 = new ImageAttributes();
attributes2.SetColorMatrix(matrix2, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(image1, new Rectangle(0, 0, image1.Width, image1.Height), 0, 0, image1.Width,
image1.Height, GraphicsUnit.Pixel, attributes1);
g.DrawImage(image2, new Rectangle(0, 0, image2.Width, image2.Height), 0, 0, image2.Width,
image2.Height, GraphicsUnit.Pixel, attributes2);
image.Save(Path.Combine(outDir,"Image" + i + ".png"),ImageFormat.Png);
}
}
}
Usage:
ImageUtility util = new ImageUtility(Image.FromFile(#"C:\path\pic1.png"), Image.FromFile(#"C:\path\pic2.png"));
util.SaveTransitions(100, #"C:\path\output"); // saves 100 images
Using winforms you can use Graphics.DrawImage, using the overload that takes an ImageAttributes parameter. That class can specify manipulation to the colour (and alpha) values.
The example on the ImageAttributes page is nearly what you want. Just draw the original and transformed one in the same place, and change the colour matrix to only change the alpha level.
I have a situation where I need to render a radio button to an System.Drawing.Image.
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(20,16);
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap))
{
RadioButtonRenderer.DrawRadioButton(g, new Point(2, 2), RadioButtonState.CheckedPressed);
}
return System.Drawing.Image.FromHbitmap(bitmap.GetHbitmap());
This works except that it's drawing the default control-grey as the background. How can I set this the background color to Transparent?
Not sure how your button looks exactly, but using the Bitmap.MakeTransparent Method with the grey color should work.
If it is not to your liking, you can try more complex color transformation using the ColorMatrix Class:
ColorMatrix matrix = new ColorMatrix();
// This will change the image's opacity.
matrix.Matrix33 = 0.5f;
ImageAttributes imgAttrs = new ImageAttributes();
imgAttrs.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(img, new Rectangle(0, 0, 50, 50), 0, 0, 50, 50, GraphicsUnit.Pixel, imgAttrs);
I have 2 forms, A and B. On the Form A, I click a button and an Image is being loaded to a PictureBox located ona the Form B. And, I want to set GrayScale to this image by:
public void SetGrayScale(PictureBox pb)
{
ColorMatrix matrix = new ColorMatrix(new float[][]
{
new float[] {0.299f, 0.299f, 0.299f, 0, 0},
new float[] {0.587f, 0.587f, 0.587f, 0, 0},
new float[] {0.114f, 0.114f, 0.114f, 0, 0},
new float[] { 0, 0, 0, 1, 0},
new float[] { 0, 0, 0, 0, 0}
});
Image image = (Bitmap)pb.Image.Clone();
ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(matrix);
Graphics graphics = Graphics.FromImage(image);
graphics.DrawImage(image,
new Rectangle(0, 0, image.Width, image.Height),
0,
0,
image.Width,
image.Height,
GraphicsUnit.Pixel,
attributes);
graphics.Dispose();
pb.Image = image;
}
This code works properly when the PictureBox is on the same form (A). But, when it is on the Form B, the OutOfMemoryException is raised. Why ?
More questions/things for you to investigate rather than an actual answer I'm afraid:
As in the comment to your answer - is the Image object correct?
If not then that implies that there's something wrong with the PictureBox object passed into this method, or that you can't access the PictureBox's Image properly.
My first thought was threading, but both forms should be in the UI thread.
Ok, I've Fixed it :)
The solution is, I had to created a Bitmap object from the OpenDialog.FileName, and later set PictureBox.Image = myBitmap
I didn't do it at the beginning, I was just setting PictureBox.Load(OpenDialog.FileName). And that was my mistake.
Ok, thank's for Your cooperation, ChrisF ! :)