C# Recolored Image Pixelated - c#

I'm attempting to load an image from the filesystem, re-color it, then save it to a Stream. The images in question are fairly simple, single-color, geometric shapes.
I have it working, but the resulting images are heavily pixelated along the edges.
I've tried System.Drawing:
var colorMap = new ColorMap
{
OldColor = Color.FromArgb(255, 255, 255, 255),
NewColor = Color.FromArgb(255, 255, 0, 0)
};
var imageAttrs = new ImageAttributes();
imageAttrs.SetRemapTable(new[] {colorMap});
var newImage = new Bitmap(image.Width, image.Height);
var graphics = Graphics.FromImage(newImage);
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.DrawImage(image,
new Rectangle(0, 0, image.Width, image.Height),
0, 0,
image.Width,
image.Height,
GraphicsUnit.Pixel,
imageAttrs);
I've also tried the ImageProcessor library, using its ReplaceColor() method, but I get the same results (although not quite as bad).
Any way to do this and preserve the nice smooth edges my original image had?

The images in question are fairly simple, single-color, geometric
shapes.
Sounds fine but the problem is that your 'Table' of colors is far too short unless the images really and truely contain only the one color you put into the map! But those images most certainly have been drawn with anti-aliasing on and therefore all anti-aliased pixels are not covered by the Table. You need to
Either use images without antialiasing, but they won't be as smooth as you want it
Or build a proper ColorMap, see below
Or write a function of your own, best using Lockbits for speed..
Or you can try to achieve the color changes with a ColorMatrix which combines speed and 'color smartness'. However not all changes lend itself to using it.. So you may want to tell us about the kind of changes you will need..
You are not alone:
I just tried the example on msdn because it looks so wrong: Saving Jpg files should not work (as it will always generate its own color tables) and look and behold it works, but only because the files created on my machine are Png files with the wrong extension! Once you add ImageFormat.Jpeg to the save it stops working..:
Original MSDN code:
myBitmap.Save("Circle2.jpg");
Changed to
myBitmap.Save("Circle2.jpg", ImageFormat.Jpeg);
Results:
To build a proper ColorMap you will need to a) loop over all pixels and collect all distinct colors (simple but slow) and then calculate the target colors (fast but not necessarily simple or well-defined.)
Here are two routines that show how to build a complete ColorMap:
List<Color> GetDistinctColors(Bitmap bmp)
{
List<Color> colors = new List<Color>();
for (int y = 0; y < bmp.Height; y++)
for (int x = 0; x < bmp.Width; x++)
{
Color c = bmp.GetPixel(x,y);
if (!colors.Contains(c)) colors.Add(c);
}
return colors;
}
List<Color> ChangeColors(List<Color> colors)
{
List<Color> newColors = new List<Color>();
foreach(Color c in colors)
{
int A = 255; // here you need..
int R = c.G; // ..to write..
int G = c.R; // ..your custom .
int B = c.B; // ..color change code!!
newColors.Add(Color.FromArgb(A,R,G,B));
}
return newColors;
}
To use it you write:
// prepare the two lists:
List<Color> colors = GetDistinctColors((Bitmap)myImage);
List<Color> newColors = ChangeColors(colors);
// Create a complete color map
ColorMap[] myColorMap = new ColorMap[colors.Count];
for (int i = 0; i < colors.Count; i++)
{
myColorMap[i] = new ColorMap();
myColorMap[i].OldColor = colors[i];
myColorMap[i].NewColor = newColors[i];
}
Note that writing the correct code for the ChangeColor function is anything but simple. Not only will you have to know what you want, you also need the right tools to achieve it. In my code example above I have done a very simplistic channel swap. This will usually not result in what you want: Neither the prime colors nor the anti-aliased pixels can be changed so simply. Instead you should transform from RGB to HSL or HSV and change the hue there! See here for a SetHue example!

Related

C# Remove gradient background from image

I have an image loaded into a Bitmap in C# with a gradient background from a document i scanned in.
An example of it could be like the picture below:
My goal in C# is now to remove the background so that I have a solid white background. Now I myself can't seem to find a way to do this. Is there a way to achieve this in a way?
Thanks in advance.
Here is a version using LockBits.
The premise is if it's not black then change it to white.
It will be magnitudes faster the GetPixel and SetPixel
It works with the raw data in memory using pointers
iterates through every pixel
Checks the color and changes it to white if needed
Saves the image
Note : obviously this will destroy any antialiasing and smoothing, it will fail for certain image types, and other assorted issues.
using (var bmp = new Bitmap(#"D:\Test.png"))
{
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppPArgb);
var white = Color.White.ToArgb();
var black = Color.Black.ToArgb();
try
{
var length = (int*)data.Scan0 + bmp.Height * bmp.Width;
for (var p = (int*)data.Scan0; p < length; p++)
if (*p != black) *p = white;
}
finally
{
// unlock the bitmap
bmp.UnlockBits(data);
bmp.Save(#"D:\Output.Bmp", ImageFormat.Bmp);
}
}
Output
If you know the gradient colors (e.g. only part of RGB color responsible for red changes) or at least the color of text (e.g. if it is always black) then you can iterate through all of image's pixels and then:
Use GetPixel() to get pixel color.
Check if it is text (black).
If it is, then move to the next pixel.
If it isn't, then change color to white with SetPixel().
For gradient it should be enough. For more complex backgrounds it would need a more complex algorithm.

Remove the black background color of a bitmap

I need to remove the black background color of a bitmap in C# VS2013.
It is just like that I draw some points on a canvas. The canvas is black and I just need to change the canvas to be transparent meanwhile keeping colorful points on it without any changes.
I got the solution at:
How to remove white background color from Bitmap
Bitmap capcha = new Bitmap(#"C:/image.png");
capcha.MakeTransparent(Color.Black);
But, the background still have a gray color like a fog covering the points on the image.
How to remove it ?
UPDATE
I used the code:
ImageAttribute imageAttribute = new ImageAttribute();
imageAttribute.SetGamma(0.5f, ColorAdjustType.Bitmap);
gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height),
0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imageAttribute );
I got same thing.
More update of C# code to draw an image :
System.Drawing.Bitmap canvasImage = new System.Drawing.Bitmap(xSize, ySize, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
canvasImage.MakeTransparent(Color.Black);
Graphics g = Graphics.FromImage(canvasImage);
System.Drawing.Bitmap tempImage = myDrawImageFunction(myPoints);
g.Clear(Color.Transparent); // this only give me an empty image without any points on it. But, if I uncomment it, I will have an image with black background.
// my understanding about g.DrawImage() is to draw points on tempImage
// after clearing the background. But, the image still have a foggy covering on the image.
g.DrawImage(tempImage, new System.Drawing.PointF(x_position, y_position));
I want to have a transparent background for "tempImage" before any points are drawn on it.
The example image has a back ground that needs to be removed but the colorful points on the image need to be kept without any changes.
This will do the job:
public Color MakeTransparent(Color c, int threshold)
{ // calculate the weighed brightness:
byte val = (byte)((c.R * 0.299f + c.G * 0.587f + c.B * 0.114f));
return val < threshold ? Color.FromArgb(0, c.R, c.G, c.B) : c;
}
You could use it in a double loop over the pixels, but for fast results you should call it from the code in this post (second part!) which uses LockBits.
Change this
ModifyHue hueChanger = new ModifyHue(MaxChannel);
to the new function:
ModifyHue hueChanger = new ModifyHue(MakeTransparent);
and call it with a suitable threshold, maybe 10 or 20..:
c = hueChanger(c, 20);
The function skips the call to the system's MakeTransparent function and directly sets the alpha channel of each pixel to 0.
If you want to create a uniform color instead of a transparent one it should be easy to modify (e.g. by returning Color.FromArgb(255, 0, 0, 0) for solid black)
Do note that, while the code in the linked post takes both 24 and 32 bbp formats you should definitely not save as JPG, as this will re-introduce artifacts and the result will not work well with e.g. a TransparencyKey color..
Instead do save it as PNG, as Hans suggests!
I hope you can modify the button code to a function :-)

Drawing image with additive blending

Question was answered. For more information, check out EDIT #4 at the end of this text.
We are currently working on a gamemaking engine which is going pretty well. I am working on the Animation Creator and was wondering if it was possible to draw an image with additive blending.
Let me explain.
We use System.Drawing library of C# and we work with Windows Forms. For now, the user is able to create his animation by importing a framed animation image (an image containing every frame of the animation) and the user is able to drag and drop these frames wherever he wants.
The actual problem is that we can't figure out how to draw a frame with the additive blending.
Here's an exemple of what Additive Blending is if you don't quite get it. I won't blame you, I have a hard time writing in english.
We are using the following method to draw on a Panel or directly on the form. For exemple here's the code to draw a tiled map for the map editor. Since the AnimationManager code is a mess, it'll be clearer with this exemple.
using (Graphics g = Graphics.FromImage(MapBuffer as Image))
using (Brush brush = new SolidBrush(Color.White))
using (Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0), 1))
{
g.FillRectangle(brush, new Rectangle(new Point(0, 0), new Size(CurrentMap.MapSize.Width * TileSize, CurrentMap.MapSize.Height * TileSize)));
Tile tile = CurrentMap.Tiles[l, x, y];
if (tile.Background != null) g.DrawImage(tile.Background, new Point(tile.X * TileSize, tile.Y * TileSize));
g.DrawRectangle(pen, x * TileSize, y * TileSize, TileSize, TileSize);
}
Is there a possible way of drawing an image with an additive drawing and if so, I'd be forever grateful if someone could point me out how. Thank you.
EDIT #1 :
For drawing images, we are using a color matrix to set hue and alph (opacity) like this:
ColorMatrix matrix = new ColorMatrix
(
new Single[][]
{
new Single[] {r, 0, 0, 0, 0},
new Single[] {0, g, 0, 0, 0},
new Single[] {0, 0, b, 0, 0},
new Single[] {0, 0, 0, a, 0},
new Single[] {0, 0, 0, 0, 1}
}
);
Maybe the color matrix can be used for additive blending?
EDIT #2 :
Just found this article by Mahesh Chand.
After further browsing, it may not be possible with a color matrix even though it can accomplish a lot regarding color transformations.
I will answer my own question if solution found.
Thank you for you help.
EDIT #3 :
XNA has a lot of documentation here about blending. I found the formula used to accomplish additive blending on each pixels of an image.
PixelColor = (source * [1, 1, 1, 1]) + (destination * [1, 1, 1, 1])
Maybe there's a way of using this formula in the current context?
I will start a 50 bounty on next edit, we really need this to work.
Thank you again for your time.
EDIT #4
Thanks to axon, now the problem is solved. Using XNA and its Spritebatch, you can accomplish Additive blending doing so :
First of all you create a GraphicsDevice and a SpriteBatch
// In the following example, we want to draw inside a Panel called PN_Canvas.
// If you want to draw directly on the form, simply use "this" if you
// write the following code in your form class
PresentationParameters pp = new PresentationParameters();
// Replace PN_Canvas with the control to be drawn on
pp.BackBufferHeight = PN_Canvas.Height;
pp.BackBufferWidth = PN_Canvas.Width;
pp.DeviceWindowHandle = PN_Canvas.Handle;
pp.IsFullScreen = false;
device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, pp);
batch = new SpriteBatch(device);
Then, when it's time to draw on the control or on the form (with the OnPaint event for example), you can use the following code block
// You should always clear the GraphicsDevice first
device.Clear(Microsoft.Xna.Framework.Color.Black);
// Note the last parameter of Begin method
batch.Begin(SpriteSortMode.BackToFront, BlendState.Additive);
batch.draw( /* Things you want to draw, positions and other infos */ );
batch.End();
// The Present method will draw buffer onto control or form
device.Present();
Either use 1) XNA (recommended for speed), or 2) use pixel-operations in C#. There may be other methods, but either of these work (I'm using each of them for 3D effects and image analysis apps (respectively) that I maintain).
Pixel Operations in C#:
Using 3 bitmaps; bmpA, bmpB, bmpC, where you want to store bmpA+bmpB in bmpC.
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
Color cA = bmpA.GetPixel(x,y);
Color cB = bmpB.GetPixel(x,y);
Color cC = Color.FromArgb(cA.A, cA.R + cB.R, cA.G + cB.G, cA.B + cB.B);
bmpC.SetPixel(x, y, cC);
}
}
The above code is very slow. A faster solution in C# could use pointers like this:
// Assumes all bitmaps are the same size and same pixel format
BitmapData bmpDataA = bmpA.LockBits(new Rectangle(0, 0, bmpA.Width, bmpA.Height), ImageLockMode.ReadOnly, bmpA.PixelFormat);
BitmapData bmpDataB = bmpB.LockBits(new Rectangle(0, 0, bmpA.Width, bmpA.Height), ImageLockMode.ReadOnly, bmpA.PixelFormat);
BitmapData bmpDataC = bmpC.LockBits(new Rectangle(0, 0, bmpA.Width, bmpA.Height), ImageLockMode.WriteOnly, bmpA.PixelFormat);
void* pBmpA = bmpDataA.Scan0.ToPointer();
void* pBmpB = bmpDataB.Scan0.ToPointer();
void* pBmpC = bmpDataC.Scan0.ToPointer();
int bytesPerPix = bmpDataA.Stride / bmpA.Width;
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++, pBmpA += bytesPerPix, pBmpB += bytesPerPix, pBmpC += bytesPerPix)
{
*(byte*)(pBmpC) = *(byte*)(pBmpA) + *(byte*)(pBmpB); // R
*(byte*)(pBmpC + 1) = *(byte*)(pBmpA + 1) + *(byte*)(pBmpB + 1); // G
*(byte*)(pBmpC + 2) = *(byte*)(pBmpA + 2) + *(byte*)(pBmpB + 2); // B
}
}
bmpA.UnlockBits(bmpDataA);
bmpB.UnlockBits(bmpDataB);
bmpC.UnlockBits(bmpDataC);
The above method requires pointers and hence must be compiled with the "unsafe" directive. Also assumes 1-byte for each of R,G, and B. Change the code to suit your pixel format.
Using XNA is a lot faster (performance) since it is hardware accelerated (by the GPU). It basically consists of the following:
1. Create the geometry needed to draw the image (a rectangle, most likely a full-screen quad).
2. Write a vertex-shader and pixel-shader. The vertex-shader can simply pass-through the geometry unmodified. Or you can apply an orthogonal projection (depending on what coordinates you want to work with for the quad). The pixel shader will have the following lines (HLSL):
float4 ps(vertexOutput IN) : COLOR
{
float3 a = tex2D(ColorSampler,IN.UV).rgb;
float3 b = tex2D(ColorSampler2,IN.UV).rgb;
return float4(a + b,1.0f);
}
There are different methods available for accessing textures. The following will also work (depending on how you want the XNA code to bind to the shader parameters):
float4 ps(vertexOutput IN) : COLOR
{
float3 a = texA.Sample(samplerState, IN.UV).xyz;
float3 b = texB.Sample(samplerState, IN.UV).xyz;
return float4(a + b,1.0f);
}
Which of the above shaders you use will depend on whether you want to use the "sampler2D" or "texture" HLSL interfaces to access the textures.
You should also be careful to use an appropriate sampler setting to ensure that no sampling (e.g. linear interpolation) is used when looking up colour values unless that's something you want (in which case use something higher-quality/higher-order).
XNA also has built-in BlendStates you can use to specify how overlapped textures will be combined. I.e. BlendState.Additive (see updated original post).

Bad text rendering using DrawString on top of transparent pixels

When rendering text into a bitmap, I find that text looks very bad when rendered on top of an area with non-opaque alpha. The problem is progressively worse as the underlying pixels become more transparent. If I had to guess I'd say that when underlying pixels are transparent, the text renderer draws any anti-aliased 'gray' pixels as solid black.
Here are some screenshots:
Text drawn on top of transparent pixels:
Text drawn on top of semi-transparent pixels:
Text drawn on opaque pixels:
Here is the code used to render the text:
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawString("Press the spacebar", Font, Brushes.Black, textLeft, textTop);
The option I used to workaround this problem was:
Graphics graphics = new Graphics();
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
There are some others useful options in TextRenderingHint
Hope it helps
There is a very simple answer to this...
g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit
If you set this before you render your text, it will come out clear. In addition, this methods supports more font sizes (The default only goes up to size 56).
Thanks for reading this post.
The first output is what you get when you draw black text on a black background, probably Color.Transparent. The 2nd was drawn on an almost-black background. The 3rd was drawn on the same background it is being displayed with.
Anti-aliasing cannot work when on a transparent background. The colors used for the anti-aliasing pixels will not blend the letter shape into the background when the text is displayed with a different background. Those pixels will now become very noticeable and make the text look very bad.
Note that SmoothingMode doesn't affect text output. It will look slightly less bad if you use a lower quality TextRenderingHint and a background color that's grayish with a alpha of zero. Only TextRenderingHint.SingleBitPerPixelGridFit avoids all anti-aliasing troubles.
Getting a perfect fix for this is very difficult. Vista's glass effect on the window title bar uses very subtle shading to give the text a well defined background color. You'd need SysInternals' ZoomIt tool to really see it. DrawThemeTextEx() function with a non-zero iGlowSize.
If you're looking for something that preserves antialiasing a bit better than GDI+ does by default, you can call Graphics.Clear with a chroma key, then manually remove the chroma artifacts that result. (See Why does DrawString look so crappy? and Ugly looking text problem.)
Here's how I ultimately ended up solving a similar problem:
static Bitmap TextToBitmap(string text, Font font, Color foregroundColor)
{
SizeF textSize;
using ( var g = Graphics.FromHwndInternal(IntPtr.Zero) )
textSize = g.MeasureString(text, font);
var image = new Bitmap((int)Math.Ceiling(textSize.Width), (int)Math.Ceiling(textSize.Height));
var brush = new SolidBrush(foregroundColor);
using ( var g = Graphics.FromImage(image) )
{
g.Clear(Color.Magenta);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawString(text, font, brush, 0, 0);
g.Flush();
}
image.MakeTransparent(Color.Magenta);
// The image now has a transparent background, but around each letter are antialiasing artifacts still keyed to magenta. We need to remove those.
RemoveChroma(image, foregroundColor, Color.Magenta);
return image;
}
static unsafe void RemoveChroma(Bitmap image, Color foregroundColor, Color chroma)
{
if (image == null) throw new ArgumentNullException("image");
BitmapData data = null;
try
{
data = image.LockBits(new Rectangle(Point.Empty, image.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
for ( int y = data.Height - 1; y >= 0; --y )
{
int* row = (int*)(data.Scan0 + (y * data.Stride));
for ( int x = data.Width - 1; x >= 0; --x )
{
if ( row[x] == 0 ) continue;
Color pixel = Color.FromArgb(row[x]);
if ( (pixel != foregroundColor) &&
((pixel.B >= foregroundColor.B) && (pixel.B <= chroma.B)) &&
((pixel.G >= foregroundColor.G) && (pixel.G <= chroma.G)) &&
((pixel.R >= foregroundColor.R) && (pixel.R <= chroma.R)) )
{
row[x] = Color.FromArgb(
255 - ((int)
((Math.Abs(pixel.B - foregroundColor.B) +
Math.Abs(pixel.G - foregroundColor.G) +
Math.Abs(pixel.R - foregroundColor.R)) / 3)),
foregroundColor).ToArgb();
}
}
}
}
finally
{
if (data != null) image.UnlockBits(data);
}
}
It's a shame GDI/GDI+ doesn't do this already, but that would be sensible, wouldn't it? :)
If you aren't able to use an unsafe context, you could easily use the same logic with Bitmap.GetPixel and Bitmap.SetPixel, though it will be significantly slower.

Resize transparent images using C#

Does anyone have the secret formula to resizing transparent images (mainly GIFs) without ANY quality loss - what so ever?
I've tried a bunch of stuff, the closest I get is not good enough.
Take a look at my main image:
http://www.thewallcompany.dk/test/main.gif
And then the scaled image:
http://www.thewallcompany.dk/test/ScaledImage.gif
//Internal resize for indexed colored images
void IndexedRezise(int xSize, int ySize)
{
BitmapData sourceData;
BitmapData targetData;
AdjustSizes(ref xSize, ref ySize);
scaledBitmap = new Bitmap(xSize, ySize, bitmap.PixelFormat);
scaledBitmap.Palette = bitmap.Palette;
sourceData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly, bitmap.PixelFormat);
try
{
targetData = scaledBitmap.LockBits(new Rectangle(0, 0, xSize, ySize),
ImageLockMode.WriteOnly, scaledBitmap.PixelFormat);
try
{
xFactor = (Double)bitmap.Width / (Double)scaledBitmap.Width;
yFactor = (Double)bitmap.Height / (Double)scaledBitmap.Height;
sourceStride = sourceData.Stride;
sourceScan0 = sourceData.Scan0;
int targetStride = targetData.Stride;
System.IntPtr targetScan0 = targetData.Scan0;
unsafe
{
byte* p = (byte*)(void*)targetScan0;
int nOffset = targetStride - scaledBitmap.Width;
int nWidth = scaledBitmap.Width;
for (int y = 0; y < scaledBitmap.Height; ++y)
{
for (int x = 0; x < nWidth; ++x)
{
p[0] = GetSourceByteAt(x, y);
++p;
}
p += nOffset;
}
}
}
finally
{
scaledBitmap.UnlockBits(targetData);
}
}
finally
{
bitmap.UnlockBits(sourceData);
}
}
I'm using the above code, to do the indexed resizing.
Does anyone have improvement ideas?
If there's no requirement on preserving file type after scaling I'd recommend the following approach.
using (Image src = Image.FromFile("main.gif"))
using (Bitmap dst = new Bitmap(100, 129))
using (Graphics g = Graphics.FromImage(dst))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(src, 0, 0, dst.Width, dst.Height);
dst.Save("scale.png", ImageFormat.Png);
}
The result will have really nice anti aliased edges
removed image shack image that had been replaced by an advert
If you must export the image in gif you're in for a ride; GDI+ doesn't play well with gif. See this blog post about it for more information
Edit: I forgot to dispose of the bitmaps in the example; it's been corrected
This is a basic resize function I've used for a few of my applications that leverages GDI+
/// <summary>
/// Resize image with GDI+ so that image is nice and clear with required size.
/// </summary>
/// <param name="SourceImage">Image to resize</param>
/// <param name="NewHeight">New height to resize to.</param>
/// <param name="NewWidth">New width to resize to.</param>
/// <returns>Image object resized to new dimensions.</returns>
/// <remarks></remarks>
public static Image ImageResize(Image SourceImage, Int32 NewHeight, Int32 NewWidth)
{
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(NewWidth, NewHeight, SourceImage.PixelFormat);
if (bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format1bppIndexed | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format4bppIndexed | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format8bppIndexed | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Undefined | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.DontCare | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format16bppArgb1555 | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format16bppGrayScale)
{
throw new NotSupportedException("Pixel format of the image is not supported.");
}
System.Drawing.Graphics graphicsImage = System.Drawing.Graphics.FromImage(bitmap);
graphicsImage.SmoothingMode = Drawing.Drawing2D.SmoothingMode.HighQuality;
graphicsImage.InterpolationMode = Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphicsImage.DrawImage(SourceImage, 0, 0, bitmap.Width, bitmap.Height);
graphicsImage.Dispose();
return bitmap;
}
I don't remember off the top of my head if it will work with GIFs, but you can give it a try.
Note: I can't take full credit for this function. I pieced a few things together from some other samples online and made it work to my needs 8^D
I think the problem is that you're doing a scan line-based resize, which is going to lead to jaggies no matter how hard you tweak it. Good image resize quality requires you to do some more work to figure out the average color of the pre-resized pixels that your resized pixel covers.
The guy who runs this website has a blog post that discusses a few image resizing algorithms. You probably want a bicubic image scaling algorithm.
Better Image Resizing
For anyone that may be trying to use Markus Olsson's solution to dynamically resize images and write them out to the Response Stream.
This will not work:
Response.ContentType = "image/png";
dst.Save( Response.OutputStream, ImageFormat.Png );
But this will:
Response.ContentType = "image/png";
using (MemoryStream stream = new MemoryStream())
{
dst.Save( stream, ImageFormat.Png );
stream.WriteTo( Response.OutputStream );
}
While PNG is definitely better that GIF, occasionally there is a use case for needing to stay in GIF format.
With GIF or 8-bit PNG, you have to address the problem of quantization.
Quantization is where you choose which 256 (or fewer) colors will best preserve and represent the image, and then turn the RGB values back into indexes. When you perform a resize operation, the ideal color palette changes, as you are mixing colors and changing balances.
For slight resizes, like 10-30%, you may be OK preserving the original color palette.
However, in most instances you'll need to re-quantize.
The primary two algorithms to pick from are Octree and nQuant. Octree is very fast and does a very good job, especially if you can overlay a smart dithering algorithm. nQuant requires at least 80MB of RAM to perform an encode (it builds a complete histogram), and is typically 20-30X slower (1-5 seconds per encode on an average image). However, it sometimes produces higher image quality that Octree since it doesn't 'round' values to maintain consistent performance.
When implementing transparent GIF and animated GIF support in the imageresizing.net project, I chose Octree. Transparency support isn't hard once you have control of the image palette.

Categories

Resources