What is the best (least resource heavy) way to fade an image in and out every 20 seconds with a duration of 1 second, against a black background (screensaver), in C# ?
(an image about 350x130px).
I need this for a simple screensaver that's going to run on some low level computers (xp).
Right now I'm using this method against a pictureBox, but it is too slow:
private Image Lighter(Image imgLight, int level, int nRed, int nGreen, int nBlue)
{
Graphics graphics = Graphics.FromImage(imgLight);
int conversion = (5 * (level - 50));
Pen pLight = new Pen(Color.FromArgb(conversion, nRed,
nGreen, nBlue), imgLight.Width * 2);
graphics.DrawLine(pLight, -1, -1, imgLight.Width, imgLight.Height);
graphics.Save();
graphics.Dispose();
return imgLight;
}
You could probably use a Color Matrix like in this example on msdn
http://msdn.microsoft.com/en-us/library/w177ax15%28VS.71%29.aspx
Instead of using a Pen and the DrawLine() method, you can use Bitmap.LockBits to access the memory of your image directly. Here's a good explanation of how it works.
Put a Timer on your form, and in the constructor, or the Form_Load,
write
timr.Interval = //whatever interval you want it to fire at;
timr.Tick += FadeInAndOut;
timr.Start();
Add a private method
private void FadeInAndOut(object sender, EventArgs e)
{
Opacity -= .01;
timr.Enabled = true;
if (Opacity < .05) Opacity = 1.00;
}
Here's my take on this
private void animateImageOpacity(PictureBox control)
{
for(float i = 0F; i< 1F; i+=.10F)
{
control.Image = ChangeOpacity(itemIcon[selected], i);
Thread.Sleep(40);
}
}
public static Bitmap ChangeOpacity(Image img, float opacityvalue)
{
Bitmap bmp = new Bitmap(img.Width, img.Height); // Determining Width and Height of Source Image
Graphics graphics = Graphics.FromImage(bmp);
ColorMatrix colormatrix = new ColorMatrix {Matrix33 = opacityvalue};
ImageAttributes imgAttribute = new ImageAttributes();
imgAttribute.SetColorMatrix(colormatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
graphics.DrawImage(img, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttribute);
graphics.Dispose(); // Releasing all resource used by graphics
return bmp;
}
It's also recommended to create another thread because this will freeze your main one.
Related
Good day
i don't know if my title is correct. sorry for my bad english
How to overlay two picturebox using c# inoder to achieve the image below, and change the opacity of upper picture box on runtime.
what i need to achieve is something like this. i have two images and i need to overlay them
first image:
enter image description here
and i have second image with a text of: Another Text on image.
and the location of the text is lower than the text location of the first image
(i can't upload more than two image because i don't have 10 reputation yet.)
i need to do like on the image below, but using two picturebox and can change the opacity in order for the second picturebox below the first one to be seen
and the output of the two image:
enter image description here
i created the output image using java. i know that i can run the jar file using c#. but the user required to changed the opacity on run time. so how can i do this?
this is the java code i used
BufferedImage biInner = ImageIO.read(inner);
BufferedImage biOutter = ImageIO.read(outter);
System.out.println(biInner);
System.out.println(biOutter);
Graphics2D g = biOutter.createGraphics();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
int x = (biOutter.getWidth() - biInner.getWidth()) / 2;
int y = (biOutter.getHeight() - biInner.getHeight()) / 2;
System.out.println(x + "x" + y);
g.drawImage(biInner, x, y, null);
g.dispose();
ImageIO.write(biOutter, "PNG", new File(output));
i hope my question is understandable. thank you
Here you go, just a sample, but blueBox is transparent (0.5):
public sealed partial class Form1 : Form
{
private readonly Bitmap m_BlueBox;
private readonly Bitmap m_YellowBox;
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
m_YellowBox = CreateBox(Color.Yellow);
m_BlueBox = CreateBox(Color.Blue);
m_BlueBox = ChangeOpacity(m_BlueBox, 0.5f);
}
public static Bitmap ChangeOpacity(Image img, float opacityvalue)
{
var bmp = new Bitmap(img.Width, img.Height);
using (var graphics = Graphics.FromImage(bmp))
{
var colormatrix = new ColorMatrix();
colormatrix.Matrix33 = opacityvalue;
var imgAttribute = new ImageAttributes();
imgAttribute.SetColorMatrix(colormatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
graphics.DrawImage(img, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, img.Width, img.Height,
GraphicsUnit.Pixel, imgAttribute);
}
return bmp;
}
private static Bitmap CreateBox(Color color)
{
var bmp = new Bitmap(200, 200);
for (var x = 0; x < bmp.Width; x++)
{
for (var y = 0; y < bmp.Height; y++)
{
bmp.SetPixel(x, y, color);
}
}
return bmp;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(m_YellowBox, new Point(10, 10));
e.Graphics.DrawImage(m_BlueBox, new Point(70, 70));
}
}
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)
If i change the colour of the image and then try to resize, it only resizes the original image. Why is this happening and how do i fix it?
Here is my code:
private PrintDocument printDoc = new PrintDocument();
private PageSettings pgSettings = new PageSettings();
private PrinterSettings prtSettings = new PrinterSettings();
Bitmap myBitmapImage; // image (bitmap) for some background mountains
Boolean isInvert = false;
Boolean isLOaded = false;
Boolean isGrayscale = false;
Boolean isThreshold = false;
Boolean isResize = false;
OpenFileDialog ofd;
Bitmap bmBack;
public EditImage()
{
printDoc.PrintPage += new PrintPageEventHandler(printDoc_PrintPage);
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
}
private void EditImage_Load(object sender, EventArgs e)
{
}
private void EditImage_Paint(object sender, PaintEventArgs e)
{
if (isLOaded == true)
{
Graphics gWindow; // reference to the graphic surface of this window
Graphics gBack; // reference to in-memory surface
bmBack = new Bitmap(Width, Height); // bitmap for window surface copy
gWindow = e.Graphics; // get our current window's surface
gBack = Graphics.FromImage(bmBack); // create surfaces from the bitmaps
gBack.DrawImage(myBitmapImage, 0, 0, Width, Height);
if (isInvert == true)
{
InvertBitmap(bmBack);
}
else if (isGrayscale == true)
{
GrayscaleBitmap(bmBack);
}
else if (isThreshold == true)
{
ThresholdBitmap(bmBack);
}
else if (isResize == true)
{
bmBack = resizeImage(bmBack, 10, 100);
}
gWindow.DrawImage(bmBack, 0, 0);
}
}
private void toolStripMenuItemLoadImage_Click(object sender, EventArgs e)
{
using (ofd = new OpenFileDialog())
{
ofd.Title = "Load Image";
if (ofd.ShowDialog() == DialogResult.OK)
{
myBitmapImage = new Bitmap(ofd.FileName);
this.Invalidate();
}
}
isLOaded = true;
}
private void GrayscaleBitmap(Bitmap bmp)
{
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format32bppRgb);
IntPtr ptr = bmpData.Scan0;
int numPixels = bmpData.Width * bmp.Height;
int numBytes = numPixels * 4;
byte[] rgbValues = new byte[numBytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, numBytes);
for (int i = 0; i < rgbValues.Length; i += 4)
{
byte gray = (byte)(.3 * rgbValues[i + 0]); //blue
gray += (byte)(.59 * rgbValues[i + 1]); //green
gray += (byte)(.11 * rgbValues[i + 2]); //red
rgbValues[i + 0] = gray; //blue
rgbValues[i + 1] = gray; //green
rgbValues[i + 2] = gray; //red
}
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, numBytes);
bmp.UnlockBits(bmpData);
}
private Bitmap resizeImage(Bitmap sourceBMP, int width, int height)
{
Bitmap result = new Bitmap(width, height);
using(Graphics g = Graphics.FromImage(result))
g.DrawImage(sourceBMP, 0, 0, width, height);
return result;
}
i also have methods which deal with when the user clicks on a button and sets the bools to appropriate values so that it calls the correct method. The images DO change colour - as intended.. but when i click on resize, i want it to resize the version of the image that has changed colour - not the original image...
There are lots of things wrong here:
resizeImage()
You should dispose of the Graphics object that you create here (wrap it in a using() statement)
Don't call invalidate from here, this function just resizes your image, you invalidate after you've changed your image and your Paint method paints the image which has now changed.
You should also think about disposing of the sourceBMP right before you return the function if you no longer have any use for it.
GrayscaleBitmap()
This looks right, but again there's no reason to invalidate here. You should invalidate after you call this method in the calling function. It makes more sense.
EditImage_Paint
You should not be calling these functions from within your Paint event. And you should not be creating a new Bitmap and a new Graphics class on each Paint. This is way more work than necessary. These functions should only execute when the data needs to be changed based on user input (user clicks a button to apply a grayscale effect).
For what you want to do, you should only need 2 bitmap variables at most. One to store the original unmodified bitmap in case you want to let the user "Reset" it or to undo any effects (most effects cause permanent data loss, you can't make a grayscale image color again). And the other to store the bitmap that gets painted. Each time the user applies an effect, it modifies the 2nd bitmap, and then calls invalidate.
All your Paint function should do is paint the 2nd bitmap:
Bitmap originalBitmap; // load from file, do not modify
Bitmap currentBitmap; // when user clicks an effect, modify this bitmap and then Invalidate afterward
private void EditImage_Paint(object sender, PaintEventArgs e)
{
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Draw the currentBitmap centered on the window
SizeF clientSize = this.ClientSize;
double zoomRatio = Math.Min(
clientSize.Width / currentBitmap.Width,
clientSize.Height / currentBitmap.Height
);
SizeF zoomedSize = new SizeF(
(float)(currentBitmap.Width * zoomRatio),
(float)(currentBitmap.Height * zoomRatio)
);
PointF imageOffset = new PointF(
(clientSize.Width - zoomedSize.Width) / 2,
(clientSize.Height - zoomedSize.Height) / 2
);
e.Graphics.Clear(Color.White);
e.Graphics.DrawImage(currentBitmap, imageOffset.X, imageOffset.Y, zoomedSize.Width, zoomedSize.Height);
}
This emulates a simple zoom effect that centers the image on the control and draws it to fit the window.
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 two Bitmaps, named largeBmp and smallBmp. I want to draw smallBmp onto largeBmp, then draw the result onto the screen. SmallBmp's white pixels should be transparent. Here is the code I'm using:
public Bitmap Superimpose(Bitmap largeBmp, Bitmap smallBmp) {
Graphics g = Graphics.FromImage(largeBmp);
g.CompositingMode = CompositingMode.SourceCopy;
smallBmp.MakeTransparent();
int margin = 5;
int x = largeBmp.Width - smallBmp.Width - margin;
int y = largeBmp.Height - smallBmp.Height - margin;
g.DrawImage(smallBmp, new Point(x, y));
return largeBmp;
}
The problem is that the result winds up transparent wherever smallBmp was transparent! I just want to see through to largeBmp, not to what's behind it.
CompositingMode.SourceCopy is the problem here. You want CompositingMode.SourceOver to get alpha blending.
Specify the transparency color of your small bitmap. e.g.
Bitmap largeImage = new Bitmap();
Bitmap smallImage = new Bitmap();
--> smallImage.MakeTransparent(Color.White);
Graphics g = Graphics.FromImage(largeImage);
g.DrawImage(smallImage, new Point(10,10);
Winform copy image on top of another
private void timerFFTp_Tick(object sender, EventArgs e)
{
if (drawBitmap)
{
Bitmap bitmap = new Bitmap(_fftControl.Width, _fftControl.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
_fftControl.DrawToBitmap(bitmap, new Rectangle(0, 0, _fftControl.Width, _fftControl.Height));
if (!fDraw)
{
bitmap.MakeTransparent();
Bitmap fftFormBitmap = new Bitmap(_fftForm.BackgroundImage);
Graphics g = Graphics.FromImage(fftFormBitmap);
g.DrawImage(bitmap, 0, 0);
_fftForm.BackgroundImage = fftFormBitmap;
}
else
{
fDraw = false;
_fftForm.Width = bitmap.Width + 16;
_fftForm.Height = bitmap.Height + 48;
_fftForm.BackgroundImage = bitmap;
}
}
}