|
I am no lawyer, but I think the patent expired few years ago...
|
|
|
|
|
It did a few YEARS ago
So png freaks put a cap on it
|
|
|
|
|
Feature request number 1 on my pet project, www.foodcandy.com after its launch six months ago was that there're no animated gifs supported on the site. I now have it working! It should go live next week. So many thanks for this code. If someone maintains an open source version I'll be glad to contribute pieces.
|
|
|
|
|
Default loop count should be -1 (don't loop), 1 means loop once (animate twice).
|
|
|
|
|
I think that forcing the GIF to crop is not very nice Here's a change to resize the GIF frames to whatever the target (or first frame) size is.
<br />
private static Image GetResizedImage(Image imgPhoto, Size ts)<br />
{<br />
<br />
int sourceWidth = imgPhoto.Width;<br />
int sourceHeight = imgPhoto.Height;<br />
int sourceX = 0;<br />
int sourceY = 0;<br />
int destX = 0;<br />
int destY = 0;<br />
<br />
float nPercent = 0;<br />
float nPercentW = 0;<br />
float nPercentH = 0;<br />
<br />
bool sourceVertical = sourceWidth < sourceHeight;<br />
bool targetVeritcal = ts.Width < ts.Height;<br />
<br />
if (sourceVertical != targetVeritcal)<br />
{<br />
int t = ts.Width;<br />
ts.Width = ts.Height;<br />
ts.Height = t;<br />
}<br />
<br />
nPercentW = ((float)ts.Width / (float)sourceWidth);<br />
nPercentH = ((float)ts.Height / (float)sourceHeight);<br />
if (nPercentH < nPercentW)<br />
{<br />
nPercent = nPercentH;<br />
destX = System.Convert.ToInt16((ts.Width -<br />
(sourceWidth * nPercent)) / 2);<br />
}<br />
else<br />
{<br />
nPercent = nPercentW;<br />
destY = System.Convert.ToInt16((ts.Height -<br />
(sourceHeight * nPercent)) / 2);<br />
}<br />
<br />
int destWidth = (int)(sourceWidth * nPercent);<br />
int destHeight = (int)(sourceHeight * nPercent);<br />
<br />
Bitmap bmPhoto = new Bitmap(ts.Width, ts.Height,<br />
PixelFormat.Format24bppRgb);<br />
bmPhoto.SetResolution(imgPhoto.HorizontalResolution,<br />
imgPhoto.VerticalResolution);<br />
<br />
Graphics grPhoto = Graphics.FromImage(bmPhoto);<br />
grPhoto.Clear(Color.White);<br />
grPhoto.InterpolationMode =<br />
InterpolationMode.HighQualityBicubic;<br />
<br />
grPhoto.DrawImage(imgPhoto,<br />
new Rectangle(destX, destY, destWidth, destHeight),<br />
new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight),<br />
GraphicsUnit.Pixel);<br />
<br />
grPhoto.Dispose();<br />
return bmPhoto;<br />
}<br />
<br />
protected void GetImagePixels()<br />
{<br />
int w = image.Width;<br />
int h = image.Height;<br />
if ((w != width)<br />
|| (h != height)<br />
)<br />
{<br />
image = GetResizedImage(image, new Size(width, height));<br />
}<br />
....<br />
<br />
public bool IsTransparent()<br />
{<br />
return transparency;<br />
}<br />
<br />
public Color GetTransparency()<br />
{<br />
Color c = Color.Empty;<br />
if (transparency)<br />
{<br />
c = Color.FromArgb(0, 0, 0, 0);
}<br />
else<br />
{<br />
c = Color.FromArgb(lastBgColor);<br />
}<br />
return c;<br />
}<br />
and finally the whole resize, very simple
<br />
<br />
public static void Resize(Stream inStream, Stream outStream, int width, int height)<br />
{<br />
GifDecoder decoder = new GifDecoder();<br />
decoder.Read(inStream);<br />
<br />
AnimatedGifEncoder encoder = new AnimatedGifEncoder();<br />
encoder.SetSize(width, height);<br />
encoder.SetFrameRate(5);<br />
encoder.SetRepeat(decoder.GetLoopCount());<br />
if (decoder.IsTransparent()) encoder.SetTransparent(decoder.GetTransparency());<br />
encoder.Start(outStream);<br />
for (int i = 0; i < decoder.GetFrameCount(); i++)<br />
{<br />
encoder.SetDelay(decoder.GetDelay(i));<br />
encoder.AddFrame(decoder.GetFrame(i));<br />
}<br />
encoder.Finish();<br />
outStream.Flush();<br />
}<br />
|
|
|
|
|
Access to the path "c:\test.gif" is denied.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.UnauthorizedAccessException: Access to the path "c:\test.gif" is denied.
Line 253: String outputFilePath = "c:\\test.gif"; //Server.MapPath("NameLogoTool/test.gif");
Line 254: AnimatedGifEncoder e = new AnimatedGifEncoder();
Line 255: e.Start(outputFilePath);
Line 256: e.SetDelay(500);
Line 257: //-1:no repeat,0:always repeat
|
|
|
|
|
Would you be interested in releasing this as an open source project? I'd be happy to set it up on SourceForge, CodePlex, or Google Code. The code you've posted works well, but it would be nice to extend it:
- allow alternate quantization methode (Octree, etc.)
- include optimizations (unsafe code, image checksums, etc.)
- leverage System.Drawing
- convert to allow use of individual functions by shifting some of the class variables to method parameters
I've already made some of these changes. It would be great to get your go-ahead to release a next version. I saw that the original Java source allowed reuse for any reason, so if I don't hear back I may just start over from the original Java source.
Jon Galloway
http://weblogs.asp.net/jgalloway
|
|
|
|
|
|
Oh, your code do not works.
Well, the follow may works.
private int NextPixel()
{
if (remaining == 0)
return EOF;
--remaining;
if (curPixel <= pixAry.GetUpperBound(0))
return pixAry[curPixel++];
return EOF;
}
Day Day Up
|
|
|
|
|
The solution for the problem with LZWEncoder making the last two pixels white is replacing "private int NextPixel()" with:
<br />
<br />
<br />
private int NextPixel() <br />
{<br />
if (remaining == 0)<br />
return EOF;<br />
<br />
--remaining;<br />
<br />
byte pix = pixAry[curPixel++];<br />
<br />
return pix & 0xff;<br />
}<br />
<br />
It is just the java code in the Refs Directory --> Gif.zip ->LZWEncoder.java
I tested it,and it works well~hoho~
|
|
|
|
|
Hi! There is a bug in your programm. It makes 2 last pixels of each frame "index 255", that often makes that pixels shining.
The bug is in private int NextPixel() in LZWEncoder.cs. If you take a look you may see that 2 last pixels returned are 0xff. Fix is obvious.
Good luck!
|
|
|
|
|
Fix is the following:
private int NextPixel()
{
if (curPixel <= pixAry.GetUpperBound(0))
{
byte pix = pixAry[curPixel++];
return pix & 0xff;
}
else
return (EOF);
}
Variable remaining in the original code is unuseful and can be removed at all.
|
|
|
|
|
Any one else having the same problem I am?
The bottom right hand corner of my animated GIFs always has a white pixel. I am 100% sure the Image i'm adding as a frame doesn't have that white pixel.
Thoughts?
Thanks.
|
|
|
|
|
Great Job gOODiDEA.NET!!! This is exactly what I have been looking for!
I modified your code a bit to support returning the animated GIF as a MemoryStream object instead of a file. This is helpful on a web server where GIF graphics are generated on the fly and it's not possible to send the output to a file first.
The modified code can be downloaded here:
http://www.thinkedgesoftware.com/download/NGif_src2.zip[^]
Feel free to incorporate the changes.
|
|
|
|
|
Sorry for my english, i'm french
Can you update the link for your modified code (
http://www.thinkedgesoftware.com/download/NGif_src2.zip) ?
Now, it doesn't exist at the server
You can send it me at etr_matty AT hotmail.com
Thank you
|
|
|
|
|
hi guy,I also need it ,if you see the msg,
please send code to ssh591@hotmail.com
thank you
|
|
|
|
|
Just change FileStream to Stream. Then use MemoryStream.
Ideally this should be rewritten with StreamWriter/Reader.
|
|
|
|
|
Does this class do any sort of opitmization? EX:
- Palette optimization, where if each frame has the same palette it doesn't have to be stored for each frame, but for the global GIF instead (GIF format allows for this right?)
- Bounding boxes that define what part of the image changes from the previous frame, so a whole frame does not need to be stored if only a little piece changes (speeds up rendering, too!)
Also is it possible to set the frame delay PER FRAME using your class? The GIF format allows this, and it is something that freeware GIF animators like UnFreez lack.
For that last piece, if not I'll probably look at the code and mod it myself to do that. Probably also code the ability to extract frame delay times if it's not already in there.
The other two would be out of my league, as I don't know that much about the GIF format to code those, unfortunately.
Looking at your example a bit closer... it might take a bit of tweaking to get it to work like other .NET classes, but I think it would be well worth the effort.
|
|
|
|
|
Well I also need that badly. The size of GIF is far too heavier. Wt abo about transparancy..?
" I have found a new world but... lost the one I belong to... "
|
|
|
|
|
Yes,the size is too heavy~~
Maybe 10 times heavy than it should be~
how to minish the size?
~~china~~
|
|
|
|
|
I also think that the image size is too big!
I resized a gif to be the same size, and it got from 20Kb to 600Kb!
Is there any way to reduce the file size?
|
|
|
|
|
BTW, I just noticed that no matter what the size and number of frames of the original image, the resulting file is always 675Kb long!
Is this ok?
|
|
|
|
|
I have done a bit of optimizing since I downloaded the source today. So far I have run into one picture that would not process correctly (it rendered and was a valid GIF for other programs, but the picture was skewed), so it's not quite fine tuned so far.
The main drawbacks that are present in the code are that is designed to handle a lot of colors really well, but is hard coded to do so. Every frame includes an index of 256 colors(768 byte color table) and starts LZW compression hard coded at 9 bit code size (when GIF forces a reset of the compression at 12 bit, back to 9 bit in this case).
First things first, though this has nothing to do with file size I thought I would mention what I believe is the unsafe code:
<code>
byte[] pixels = new Byte[3 * image.Width * image.Height];
Bitmap tempBitmap = new Bitmap(image);
BitmapData bitmapData = tempBitmap.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
Marshal.Copy(bitmapData.Scan0, pixels, 0, pixels.Length);
tempBitmap.UnlockBits(bitmapData);
</code>
After this, you will be dealing with BGR pixels (bitmaps standard).
When referring to these in the code I usually used something to the manner of
<code>
private int getPixel(int index, byte[] pixels)
{
return pixels[index*3]<<16+pixels[index*3+1]<<8+pixels[index*3+2];
}
</code>
If you are interested in shrinking the table size, this will require a slight re-write of the color analysis and the palette writing. I decided to use an ArrayList to store my palettes and keep track of number of actual colors that existed in the image. I would probably suggest a Hash Table however, with the current count of the hash table stored as the value so that you can take advantage of the hashing... I do not know how well this would increase speed, and as of now most questions are related to size concern.
During the analyzing phase, you can loop through all the pixels in the array and keep track of how many actual colors are present in the image. In the following example the ArrayList is named smallTab(small table).
<code>
for(int i=0;i<nPix;i++)
{
int c = getPixel(i, pixels);
if (!smallTab.Contains(c))
smallTab.Add(c);
if (smallTab.Count == 256)
break;
}
...
for (int i = 0; i < nPix; i++)
g.indexedPixels[i] = getSmallIndex(g.getPixel(i),g);
</code>
To get the values used for the palSize and bitDepth, I sort of did some very crude coding. PalSize is BitDepth-1, and these two values are used for defining the length of the local color table and initializing the LZW compression algorithm respectively. The following code just tests for what the highest bit is and stores it for those algorithms.
<code>
colorTableSize = smallTab.Count-1;
int a = 0;
for (int i = 0; i < 8; i++)
if ((colorTableSize & (1 << i)) != 0) a = i;
colorDepth = a + 1;
palSize = a;
</code>
This will shrink the size of the LZW compression by allowing a lot more slots for it's dictionary before it has to reset. To have the color palette printed out correctly, you have to print out the ArrayList and then fill in all of the blanks that follow (this was another section that was hard coded). The reason that the bytes are printed out from least to most significant is because I preserved BGR color throughout the program instead of converting it around a lot.
<code>
foreach (int color in smallTab)
{
fs.WriteByte((byte)(color & 0xFF));//R
fs.WriteByte((byte)((color & 0xFF00) >> 8));//G
fs.WriteByte((byte)((color & 0xFF0000) >> 16));//B
}
int remain = (1 << (palSize + 1)) - colorTableSize - 1;
for (int i = 0; i < (remain); i++)
{
fs.WriteByte(0);
fs.WriteByte(0);
fs.WriteByte(0);
}
</code>
To do a lot of the other optimization methods that are used on gif, you have to have the ability to store either the current status of the image (a method I am going to attempt) that has the pixel data for what the image should appear like in that frame, or at the very least the previous frame along with the current frame. You compare the two images and zero out, or set to whatever transparent color you wish, all of the bytes that are the same. What you are left with is a much less detailed frame that that will have a lot of the same color with will shrink in LZW compression.
To do this, it helps to create objects for each frame, so that you can associate settings with individual bitmap data, and then write them all out at once. This allows you to also flag which ones you wish to set to the global color table, so that during the pixel analysis routine these use the colorTable of the parent GifAnimationEncoder instead of their own local table.
A final method that can reduce image size a little bit (not as much as the transparency comparison which can reduce color count did) is that once you have done the comparison, you can scan the rows and columns of the resulting pixel array to see how far left,right,top,and bottom the resulting picture actually is. You can copy this to a new array, reset the height and width values, and then change The Image Descriptor values:
<code>
protected void WriteImageDesc()
{
fs.WriteByte(0x2c); // image separator
WriteShort(left,fs); // image position x,y = 0,0
WriteShort(top, fs);
WriteShort(width, fs); // image size
WriteShort(height, fs);
// packed fields
byte b = (byte)palSize;
if (!useGlobalTable) b += 0x80;
fs.WriteByte(b);
}
</code>
Using this shrink wrap method, color palette reduction, and global/local color table control, I was able to create a 1000 frame, 300 x 100 px animation (black and white, so not really impressive) for 76.2k. That is 9.76k image descriptors, 7.8k Graphics Control data... so 58.64 k for the images about. Changing to 3 or 4 colors added only 14k, still averaging quite a small amount. Actually saving a 256 color image that has a change of over 200 x 200 px does add quite a bit of time to the process and did yield a 14.2 mb file... I have yet to see if transparency comparison will help at all. Using global color table only saved ~750k
As for time saving, it is a helpful feature to add to the code where you can cache an array of pixel data that has LZW compression if you will be using that frame again later at any X,Y coordinate, such as scrolling or repeated animation and movement over a fixed color background. I am unsure if Disposal Method 3 would allow it over a background image.
-- modified at 5:22 Saturday 3rd March, 2007
|
|
|
|
|
Hi Pheonix
Do you have the code that performs the file size reductions you can [post anywhere?
Jim
|
|
|
|
|