I am using MagickNet for image manipulation in my ASP.NET C# project. My issue is that I am uploading a PNG image with transparency and when I convert it to JPEG, I get a black background with some white spots instead of a white background for the transparent part.
Stream su = upload.FileContent;
MagickNet.Image testimage = new MagickNet.Image(su);
testimage.Filter = FilterType.LanczosFilter;
testimage.Compression = CompressionType.JPEGCompression;
testimage.QuantizeDither = false;
testimage.BackgroundColor = new Color(System.Drawing.Color.White);
testimage.Resize( new System.Drawing.Size(Convert.ToInt32(testimage.Size.Width * 0.4), Convert.ToInt32(testimage.Size.Height * 0.4)));
testimage.Write(System.Web.HttpContext.Current.Server.MapPath(".") + "\\temp\\" + DateTime.Now.Hour + "-" +DateTime.Now.Minute + "-" + DateTime.Now.Second + ".jpg");
su.Close();
su.Dispose();
testimage.Dispose();
Magick.Term();
I played with it and always get the wrong result that I am after. Sometimes I get a transparent background but some parts of the image at the outer region have white dots. I also resize the image to be smaller than what it is. I think the re-sizing it causing the issue.
update: this is caused because of the resizing for some reason. Without resizing it works. Having said that, I need to resize, so I need it to work with it.
Thanks.
Try to composite onto a white background image.
Image bg = new Image(testimage.Size, new ColorRGB(255, 255, 255));
testimage = bg.Composite(testimage, 0, 0);
First of all, it is better to create your MagickImage object with the desirable size the speed of reading the file/stream with the required size can in some situation 100 times faster. You may not have that error.
using(var testimage = new MagickImage(yourstream/yourFileAddress, width, height)
{
....
}
But if you convert the MagickImage to Bitmap and then save the bitmap as jpg you can see that the image has a white background
using (var testBitmap = testimage.ToBitmap())
{
testBitmap.Save(#"d:\temp.jpg");
}
also using is much better that calling dispose member function. Because if your code throw exception before it reaches the dispose call your object will remain in the memory. But with using, if program jump out of the block the object will be disposed.
Related
this may be an amateur question but I'm still stuck...
I have a background bitmap image, and then need to super-impose a few smaller bitmaps (mostly qr codes). Things work for the 1st insert, and then it breaks. It compiles OK, but it fails on the new Bitmap line with a Exception Unhandled message System.ArgumentException: 'Parameter is not valid.'
The code is something like
Bitmap Background_bmp= new Bitmap(File_name);
Graphics Background_gfx = Graphics.FromImage(Background_bmp);
for (i=1;i<=4;i++)
{
Bitmap Insert_image = new Bitmap(File_name[i]);
Print_doc_gfx.DrawImage(Insert_image, blablabla (scaling and positioning);
Insert_image.Dispose();
}
Background_bmp.Save("C:\\Total image.bmp");
Background_gfx.Dispose();
Background_bmp.Dispose();
Simple enough, and yet it doesn't work. I'm pretty sure the breakage is over the repeated "new" in the "new Bitmap" piece, but I don't know how to declare once and use many times when it comes to bitmaps... Like I said, amateur question...
The parts you posted from your code do not appear to be causing the problem. It's most likely caused by other parts of the code, such as the coordinates of the image insertion or something totally different.
I tested using the following code with one large image and 4 small images and it worked without problems:
string File_name = "background.png";
string[] File_names = new string[] { "img1.png", "img2.png", "img3.png", "img4.png" };
Bitmap Background_bmp = new Bitmap(File_name);
// can also use empty all-black image like the following line:
// Bitmap Background_bmp = new Bitmap(800, 600, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Graphics Background_gfx = Graphics.FromImage(Background_bmp);
for (int i = 1; i <= File_names.Length; i++)
{
Bitmap Insert_image = new Bitmap(File_names[i - 1]);
Background_gfx.DrawImage(Insert_image, i * 150, i * 100);
Insert_image.Dispose();
}
Background_gfx.Dispose();
Background_bmp.Save("Total_image.png", System.Drawing.Imaging.ImageFormat.Png);
Background_bmp.Dispose();
anyone could tell me why this generate a black only animated gif?
the code also output each in memory generated gif to show that they are different
public static void Test()
{
Image<Rgba32> img = null;
Image<Rgba32> gif = null;
TextGraphicsOptions textGraphicsOptions = new TextGraphicsOptions(true);
SolidBrush<Rgba32> brushYellow = new SolidBrush<Rgba32>(Rgba32.Yellow);
FontCollection fonts = new FontCollection();
fonts.Install(fontLocation);
Font font = fonts.CreateFont("Liberation Mono", PngFontHeight, FontStyle.Regular);
gif = new Image<Rgba32>(400, 400);
for (int i = 0; i < 10;++i)
{
img = new Image<Rgba32>(400, 400);
img.Mutate(x => x.Fill(Rgba32.Black));
img.Mutate(x => x.DrawText(textGraphicsOptions, i.ToString(), font, brushYellow, new PointF(1,1)));
gif.Frames.AddFrame(img.Frames[0]);
using (FileStream fs = File.Create(Path.Join(Program.workingDirectory, string.Format("Test-{0}.gif", i))))
{
img.SaveAsGif(fs);
}
img.Dispose();
}
using (FileStream fs = File.Create(Path.Join(Program.workingDirectory, "Test.gif")))
{
gif.SaveAsGif(fs);
}
}
if I code it to load each individual physical file using this code it make the animated gif as expected.
I want to create the animated gif in memory only.
When you create an Image<>...
gif = new Image<Rgba32>(400, 400);
...gif.Frames[0] is a "transparent black" frame (each pixel's RGBA value is #00000000). The additional frames you create in your for loop and add with...
gif.Frames.AddFrame(img.Frames[0]);
...become gif.Frames[1] through gif.Frames[10], for a total of 11 frames.
The GIF encoder uses GifColorTableMode to decide if a color table is generated for each frame or the color table for the first frame is used for all frames. The combination of the default value, GifColorTableMode.Global, plus that first transparent frame results in an 11-frame .gif file with only one color, that same "transparent black". This is why your yellow text doesn't appear and every frame looks the same.
To solve this, at some point before you save the file you need to remove that initial transparent frame so it doesn't influence color table calculations and because it's not part of your animation, anyways...
gif.Frames.RemoveFrame(0);
You may also want to change to GifColorTableMode.Local so your .gif file contains color tables reflecting all of the colors rendered...
gif.MetaData.GetFormatMetaData(GifFormat.Instance).ColorTableMode = GifColorTableMode.Local;
...although your 10 frames each use almost the same set of colors so if file size is a greater concern than color representation you might leave that property alone. Generating your 400 × 400 animation with GifColorTableMode.Global produces a 9,835-byte file whereas GifColorTableMode.Local produces a 16,703-byte file; 70% bigger but I can't tell the difference between them.
Related issue on GitHub
By the way, since I found this along the way, if you wanted to change the duration of the animated frames you would do so using another GetFormatMetaData() method similar to the one shown above...
GifFrameMetaData frameMetaData = img.MetaData.GetFormatMetaData(GifFormat.Instance);
frameMetaData.FrameDelay = 100;// 1 second
I am currently using this code to screen capture the panel I created, but whenever I am saving it, the quality is bad. Is there any way to maintain the good quality when saving it?
I tried resizing the panel but the result is still the same.
I tried doing a normal screen shot with the snipping tool and it also has the same result with the codes I use.
Any suggestions? or help?
private void SaveControlImage(Control theControl)
{
snapCount++;
int width = panel1.Size.Width;
int height = panel1.Size.Height;
Bitmap bm = new Bitmap(width, height, PixelFormat.Format64bppPArgb);
panel1.DrawToBitmap(bm, new Rectangle(0, 0, width, height));
//bm.Save(#"D:\TestDrawToBitmap.bmp", ImageFormat.Bmp);
bm.Save(deskTopPath + #"/Offer_" + snapCount.ToString() + "_" + DateTime.Now.ToString("yyyyMMdd") + #".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
Like here, it looks pixelated if you compare it to what you're reading now (like this website). I tried to screen cap the form but it looks like the uploaded picture so its useless
Screenshot:
Bitmap bmp= new Bitmap(controls.Width, controls.Height-50, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics Grap = Graphics.FromImage(bmp);
Grap.CopyFromScreen(PointToScreen(controls.Location).X, PointToScreen(controls.Location).Y, 0, 0, AnhDoThi.Size, CopyPixelOperation.SourceCopy);
SaveFileDialog save = new SaveFileDialog();
save.Filter = "JPEG|*.jpg";
DialogResult tl = save.ShowDialog();
if (tl == DialogResult.OK)
{
bmp.Save(save.FileName);
MessageBox.Show("Completed !");
}
This is what I use to save a screenshot:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
private void SaveControlAsImage(Control control, string path)
{
Bitmap bitmap = new Bitmap(control.Width, control.Height);
control.DrawToBitmap(bitmap, control.Bounds);
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite))
{
/* using ImageFormat.Png or ImageFormat.Bmp saves the image with better quality */
bitmap.Save(fs, ImageFormat.Png);
}
}
Control screenshot using Windows Snipping Tool
Control screenshot using SaveControlAsImage (ImageFormat.Png)
Control screenshot using SaveControlAsImage (ImageFormat.Jpeg)
It's not the best quality (just a little blurry), but this removes the distortion around the text and keeps the right color.
Sidenote: You're not using the parameter (theControl) passed to your method, so you might as well remove it (not recommended), or change all the calls to panel1 inside the method to theControl and call the method like
this.SaveControlImage(this.panel1);
Also, when you're accessing just a property (i.e. this.panel1.Size.Width) it's better to just call the property instead of assign it to a variable. If you're calling a method and getting a value back (which you'll be using more than once), you must assign it to a variable (i.e. int arrayCount = array.Count()).
Windows Forms is not using vector graphics to draw the user interface. Therefore, all you can get generically is to draw the control to a Bitmap instead of to the screen. This works independently of whether your control is visible on the screen, but there is not more than that. If you want a higher-resolution image from a Windows Forms control, the only way is to resize the control and hope that it supports zooming.
Here's my buffer for an animation:
Bitmap PixBuffer;
Here's how I create it:
PixBuffer = new Bitmap(ClientRectangle.Width, ClientRectangle.Height, PixelFormat.Format32bppArgb);
Here's how I draw on it:
Graphics Renderer { get { return Graphics.FromImage(PixBuffer); } }
To make long story short. It works. I draw. I see changes. I use the bitmap as BackgroundImage for a window. Since the window has DoubleBuffered = true, it's silky smooth and fast.
OK, and the WTF part. I try to clone a slice of my bitmap, or even whole thing:
PixBuffer = (Bitmap)PixBuffer.Clone();
It doesn't make much sense, it should do nothing with what's displayed. But guess what - the clone is EMPTY! Exactly the same result if I try to draw PixBuffer on a new bitmap. The contents of PixBuffer is displayed. It can be even stretched as windows background. But I see no way to copy it. RotateFlip has no effect too.
What am I doing wrong? How to get pixel data of what I drew?
Freeze = (Bitmap)PixBuffer.Clone();
using (var g = Graphics.FromImage(Freeze)) {
g.FillRectangle(BrushF, 0, 0, 100, 100);
g.CompositingMode = CompositingMode.SourceOver;
g.Dispose();
}
var test = (Bitmap)Freeze.Clone();
BackgroundImage = test;
When I set PixBuffer as BackgroundImage - I get my drawn image. When I set Freeze as BackgroundImage - I get a square.
Then, if I clone Freeze to let's say Freeze1 - I still get my square, so cloning actually works on some bitmaps. But on PixBuffer NO JOY!
PixBuffer is not drawn in one frame. It is drawn as progressing animation during ca 30s. After animation completes - I have still screen - this screen I want to have as a normal bitmap to manipulate (like scaling and such). It seems like PixBuffer is write-only. I can still draw on it, but I can't copy anything from it.
I even tried to convert it to Icon and then back to Bitmap - but it's exactly the same like I was doing operations on empty Bitmap object.
But it IS NOT EMPTY! I tested it. I removed the BackgroundImage. I set another image in its place. And then I set PixBuffer as BacgroundImage again - and it is not empty, there is all I drew.
I'm missing something.
It's one of those very nasty bugs in code.
void RenderFrame(object sender, EventArgs e) {
using (var r = Graphics.FromImage(PixBuffer)) r.Clear(Color.Transparent);
var end = false;
for (var i = 0; i < Speed; i++) if (end = !UnmaskOne()) break;
RenderText();
RenderPattern();
if (end) FreezeContent = true;
Refresh();
}
I tried to copy my bitmap in UnmaskOne() method, which is called directly after clearing the frame, since this method can detect, if there's nothing more to unmask. However I had to wait with copying the bitmap - it should be drawn first with RenderText() and RenderPattern() methods. No magic here. Just plain human error.
I am generating a tile from within my application and when its displayed the background image that its using as the basis fro the the tile has lost its transparency (and therefore its not picking up the theme color.
The background image has an icon on it and is transparent - when I use it as the standard Tile (i.e. not generate an image with it ) thens its fine and the transparency is all good..
But when I use it as the background image and add my own container over it then its not transparent the background is showing as black.
The relevant code is as follows:
// [...]
var container = new Grid();
if (isWide)
{
container = CreateContainerWide(tileInfo);
}
else
{
container = CreateContainerMedium(tileInfo);
}
// Add the background
container.Background = new ImageBrush
{
ImageSource = background,
Opacity = opacity
};
// Force the container to render itself
container.Arrange(new Rect(0, 0, width, height));
// Write the image to disk and return the filename
return WriteShellTileUIElementToDisk(container, baseFileName);
}
static string WriteShellTileUIElementToDisk(UIElement element, string baseFileName)
{
var wb = new WriteableBitmap(element, null);
// All content must be in this sub-folder of IsoStore
string fileName = SharedImagePath + baseFileName + ImageExtension;
var stream = new IsolatedStorageFileStream(fileName, System.IO.FileMode.Create, Isf);
// Write the JPEG using the standard tile size
// Sometimes the bitmap has (0,0) size and this fails for unknown reasons with an argument exception
if (wb.PixelHeight > 0)
wb.SaveJpeg(stream, wb.PixelWidth, wb.PixelHeight, 0, JpegQuality);
else
{
Debug.WriteLine("Can't write out file because bitmap had 0,0 size; not sure why");
// indicate that there is an issue
fileName = null;
}
stream.Close();
// Return the filename
return fileName;
}
Doesn't seem to make any difference as to what I set the Opacity of the ImageBrush to.
If I used a solid color rataher than a transparent layer then its all fine. Somehow the creation of the png is losing the transparency.
Any Ideas?
thanks
This answer might be helpful. Instead of saving a JPG you can save the image as a PNG which does support transparency. The library mentioned in the answer is quite useful.