使用 dotimage 调整动画 gif 的大小

发布于 2024-11-24 10:30:25 字数 4005 浏览 6 评论 0原文

我有一段代码可以调整 GIF 动画的大小。 如果有帮助,代码将始终将图像大小调整为较小的尺寸。 (现在不需要将它们变得更大)

我正在使用 Atalasoft 的 dotimage 库及其示例代码来进行实际的重采样。 该代码应该从磁盘读取动画 gif,迭代帧并将每个帧的大小调整为新的大小。 当 gif 动画包含相同大小的帧时,这种方法效果很好,但使用不同大小的帧调整动画大小会破坏动画(调整大小后帧不会正确地相互重叠),我认为这是因为代码没有计算新的偏移量正确。

我认为这行代码没有正确计算偏移量: 点 point = new Point((int)(frame.Location.X * 比例), (int)(frame.Location.Y * 比例));

这是完整的调整大小例程:

    static private void GenerateGifImage(FileStream fileStream, int OutputWidth, int OutputHeight)
    {            
        // MemoryStream InputStream = new MemoryStream();
        FileStream InputStream = fileStream;
        // fileStream.Write(InputStream.GetBuffer(), 0, (int)InputStream.Position);
        // InputStream.Seek(0, SeekOrigin.Begin);
        Image InputImage = Image.FromStream(InputStream, true, false);

        // this will invalidate the underlying image object in InputImage but the class properties 
        // will still accessible until the object is disposed
        InputStream.Seek(0, SeekOrigin.Begin);

        ImageInfo imageInfo = RegisteredDecoders.GetImageInfo(InputStream);
        InputStream.Seek(0, SeekOrigin.Begin);

        GifDecoder gifDecoder = new GifDecoder();
        int count = gifDecoder.GetFrameCount(InputStream);

        GifFrameCollection gifFrameCollection = new GifFrameCollection();
        gifFrameCollection.Height = OutputHeight;
        gifFrameCollection.Width = OutputWidth;
        // gifFrameCollection.Height = gifDecoder.Frames.Height;
        // gifFrameCollection.Width = gifDecoder.Frames.Width;

        double ratio;
        if (InputImage.Height > InputImage.Width)
        {
            ratio = (double)OutputHeight / (double)InputImage.Height;
        }
        else
        {
            ratio = (double)OutputWidth / (double)InputImage.Width;
        }

        for (int i = 0; i < count; i++)
        {
            GifFrame frame = gifDecoder.Frames[i];

            Rectangle rectangle = new Rectangle(Point.Empty, frame.Image.Size);

            int frameWidth = (int)(frame.Image.Width * ratio);
            int frameHeight = (int)(frame.Image.Height * ratio);

            // account for erratic rounding, seems illogical but has happened earlier when using floats instead of doubles 
            if (frameWidth > OutputWidth)
            {
                frameWidth = OutputWidth;
            }
            if (frameHeight > OutputHeight)
            {
                frameHeight = OutputHeight;
            }

            Size size = new Size(frameWidth, frameHeight);
            // only resize if we have a measureable dimension
            if (size.Width > 0 && size.Height > 0)
            {
                // ResampleCommand resampleCommand = new ResampleCommand(rectangle, size, ResampleMethod.NearestNeighbor);
                ResampleCommand resampleCommand = new ResampleCommand(rectangle, size, ResampleMethod.NearestNeighbor);
                AtalaImage atalaImage = resampleCommand.Apply(frame.Image).Image;
                // save the image for debugging
                // atalaImage.Save("frame" + i.ToString() + ".gif", ImageType.Gif, null);
                // frame.Image.Save("frame-orig" + i.ToString() + ".gif", ImageType.Gif, null);

                // AtalaImage atalaImage = frame.Image;
                Point point = new Point((int)(frame.Location.X * ratio), (int)(frame.Location.Y * ratio));
                // Point point = new Point((int)(frame.Location.X), (int)(frame.Location.Y));
                gifFrameCollection.Add(new GifFrame(atalaImage, point, frame.DelayTime, frame.Interlaced, frame.FrameDisposal, frame.TransparentIndex, frame.UseLocalPalette));
            }
        }
        FileStream saveStream = new FileStream("resized.gif", FileMode.Create, FileAccess.Write, FileShare.Write);
        GifEncoder gifSave = new GifEncoder();
        gifSave.Save(saveStream, gifFrameCollection, null);
        saveStream.Close();
    }

I have a piece of code which resizes animated gifs.
if it helps the code will always resize images to a smaller size.
(there's no need to make them bigger for now)

I am using Atalasoft's dotimage library and their example code to do the actual resampling.
The code is supposed to read an animated gif from disk, iterate through the frames and resize each frame to the new size.
This works fine when the gif animation contains frames of the same size but resizing a animation with different sized frames breaks the animation (the frames don't overlap each other correctly after resizing), I think it's because the code is not computing the new offsets correctly.

I think it's this line of code which is not computing the offsets right:
Point point = new Point((int)(frame.Location.X * ratio), (int)(frame.Location.Y * ratio));

Here's the complete resize routine:

    static private void GenerateGifImage(FileStream fileStream, int OutputWidth, int OutputHeight)
    {            
        // MemoryStream InputStream = new MemoryStream();
        FileStream InputStream = fileStream;
        // fileStream.Write(InputStream.GetBuffer(), 0, (int)InputStream.Position);
        // InputStream.Seek(0, SeekOrigin.Begin);
        Image InputImage = Image.FromStream(InputStream, true, false);

        // this will invalidate the underlying image object in InputImage but the class properties 
        // will still accessible until the object is disposed
        InputStream.Seek(0, SeekOrigin.Begin);

        ImageInfo imageInfo = RegisteredDecoders.GetImageInfo(InputStream);
        InputStream.Seek(0, SeekOrigin.Begin);

        GifDecoder gifDecoder = new GifDecoder();
        int count = gifDecoder.GetFrameCount(InputStream);

        GifFrameCollection gifFrameCollection = new GifFrameCollection();
        gifFrameCollection.Height = OutputHeight;
        gifFrameCollection.Width = OutputWidth;
        // gifFrameCollection.Height = gifDecoder.Frames.Height;
        // gifFrameCollection.Width = gifDecoder.Frames.Width;

        double ratio;
        if (InputImage.Height > InputImage.Width)
        {
            ratio = (double)OutputHeight / (double)InputImage.Height;
        }
        else
        {
            ratio = (double)OutputWidth / (double)InputImage.Width;
        }

        for (int i = 0; i < count; i++)
        {
            GifFrame frame = gifDecoder.Frames[i];

            Rectangle rectangle = new Rectangle(Point.Empty, frame.Image.Size);

            int frameWidth = (int)(frame.Image.Width * ratio);
            int frameHeight = (int)(frame.Image.Height * ratio);

            // account for erratic rounding, seems illogical but has happened earlier when using floats instead of doubles 
            if (frameWidth > OutputWidth)
            {
                frameWidth = OutputWidth;
            }
            if (frameHeight > OutputHeight)
            {
                frameHeight = OutputHeight;
            }

            Size size = new Size(frameWidth, frameHeight);
            // only resize if we have a measureable dimension
            if (size.Width > 0 && size.Height > 0)
            {
                // ResampleCommand resampleCommand = new ResampleCommand(rectangle, size, ResampleMethod.NearestNeighbor);
                ResampleCommand resampleCommand = new ResampleCommand(rectangle, size, ResampleMethod.NearestNeighbor);
                AtalaImage atalaImage = resampleCommand.Apply(frame.Image).Image;
                // save the image for debugging
                // atalaImage.Save("frame" + i.ToString() + ".gif", ImageType.Gif, null);
                // frame.Image.Save("frame-orig" + i.ToString() + ".gif", ImageType.Gif, null);

                // AtalaImage atalaImage = frame.Image;
                Point point = new Point((int)(frame.Location.X * ratio), (int)(frame.Location.Y * ratio));
                // Point point = new Point((int)(frame.Location.X), (int)(frame.Location.Y));
                gifFrameCollection.Add(new GifFrame(atalaImage, point, frame.DelayTime, frame.Interlaced, frame.FrameDisposal, frame.TransparentIndex, frame.UseLocalPalette));
            }
        }
        FileStream saveStream = new FileStream("resized.gif", FileMode.Create, FileAccess.Write, FileShare.Write);
        GifEncoder gifSave = new GifEncoder();
        gifSave.Save(saveStream, gifFrameCollection, null);
        saveStream.Close();
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

孤凫 2024-12-01 10:30:25

我在 Atalasoft 工作,

我对此进行了研究——您的代码绝对正确,并且可以在大小不等的框架上正常工作。您计算的点是正确的。

问题是,在 3 帧 GIF 中,第二帧和第三帧被精确地叠加在第一帧之上,并使用非常复杂的透明蒙版通过它们显示第一帧。当您的图像被重新采样到新的尺寸时,蒙版可能不再精确 - 因为您将大小调整为宽度和高度上只有一个像素的差异,所以该蒙版无法匹配。

此问题有多种解决方案

  1. 将帧 2 覆盖到帧 1 上,然后重新采样并使用该图像代替
  2. 执行 #1,但然后提取帧 2 的矩形
  3. 使用裁剪而不是重新采样 - 这似乎是最好的,因为它只有 1 个像素。

我为你编写了#3——效果很好

    static private void GenerateGifImage(FileStream fileStream, int OutputWidth, int OutputHeight)
    {            
        // MemoryStream InputStream = new MemoryStream();
        FileStream InputStream = fileStream;
        // fileStream.Write(InputStream.GetBuffer(), 0, (int)InputStream.Position);
        // InputStream.Seek(0, SeekOrigin.Begin);
        Image InputImage = Image.FromStream(InputStream, true, false);

        // this will invalidate the underlying image object in InputImage but the class properties 
        // will still accessible until the object is disposed
        InputStream.Seek(0, SeekOrigin.Begin);

        ImageInfo imageInfo = RegisteredDecoders.GetImageInfo(InputStream);
        InputStream.Seek(0, SeekOrigin.Begin);

        GifDecoder gifDecoder = new GifDecoder();
        int count = gifDecoder.GetFrameCount(InputStream);

        GifFrameCollection gifFrameCollection = new GifFrameCollection();
        gifFrameCollection.Height = OutputHeight;
        gifFrameCollection.Width = OutputWidth;

        double ratio;
        if (InputImage.Height > InputImage.Width)
        {
            ratio = (double)OutputHeight / (double)InputImage.Height;
        }
        else
        {
            ratio = (double)OutputWidth / (double)InputImage.Width;
        }

        for (int i = 0; i < count; i++)
        {
            GifFrame frame = gifDecoder.Frames[i];

            Rectangle rectangle = new Rectangle(Point.Empty, frame.Image.Size);

            int newframeWidth = frame.Image.Width;
            int newframeHeight = frame.Image.Height;
            if (newframeWidth > OutputWidth || newframeHeight > OutputHeight)
            {
                newframeWidth = (int)(frame.Image.Width * ratio);
                newframeHeight = (int)(frame.Image.Height * ratio);
            }

            // account for erratic rounding, seems illogical but has happened earlier when using floats instead of doubles 
            if (newframeWidth > OutputWidth)
            {
                newframeWidth = OutputWidth;
            }
            if (newframeHeight > OutputHeight)
            {
                newframeHeight = OutputHeight;
            }

            Size size = new Size(newframeWidth, newframeHeight);
            // only resize if we have a measureable dimension
            if (size.Width > 0 && size.Height > 0)
            {
                //ResampleCommand resampleCommand = new ResampleCommand(rectangle, size, ResampleMethod.);
                AtalaImage atalaImage = frame.Image;
                if (newframeWidth != frame.Image.Width || newframeHeight != frame.Image.Height)
                {
                    CropCommand command = new CropCommand(new Rectangle(new Point(0, 0), size));
                    atalaImage = command.Apply(frame.Image).Image;
                }
                // AtalaImage atalaImage = frame.Image;
                Point point = new Point((int)(frame.Location.X), (int)(frame.Location.Y));
                // Point point = new Point((int)(frame.Location.X), (int)(frame.Location.Y));
                gifFrameCollection.Add(new GifFrame(atalaImage, point, frame.DelayTime, frame.Interlaced, frame.FrameDisposal, frame.TransparentIndex, frame.UseLocalPalette));
            }
        }
        FileStream saveStream = new FileStream("resized.gif", FileMode.Create, FileAccess.Write, FileShare.Write);
        GifEncoder gifSave = new GifEncoder();
        gifSave.Save(saveStream, gifFrameCollection, null);
        saveStream.Close();
    }

I work at Atalasoft

I looked into this -- your code is absolutely right and would work on frames of unequal size just fine. The point you are calculating is correct.

The problem is that in your 3 frame GIF, your second frame and third frame are precisely made to be overlaid on top of the first and use a very complex transparent mask to show the first frame through them. When your image is resampled to a new size, the mask might not be precise any more -- since you are resizing to just a one pixel difference on width and height, there is no way that this mask could match.

There are several solutions to this problem

  1. Overlay frame 2 onto frame 1, then resample and use that image instead
  2. Do #1, but then extract the rectangle of frame 2
  3. Use crop instead of resample -- this seems best since it's just 1 pixel.

I coded up #3 for you -- it works well

    static private void GenerateGifImage(FileStream fileStream, int OutputWidth, int OutputHeight)
    {            
        // MemoryStream InputStream = new MemoryStream();
        FileStream InputStream = fileStream;
        // fileStream.Write(InputStream.GetBuffer(), 0, (int)InputStream.Position);
        // InputStream.Seek(0, SeekOrigin.Begin);
        Image InputImage = Image.FromStream(InputStream, true, false);

        // this will invalidate the underlying image object in InputImage but the class properties 
        // will still accessible until the object is disposed
        InputStream.Seek(0, SeekOrigin.Begin);

        ImageInfo imageInfo = RegisteredDecoders.GetImageInfo(InputStream);
        InputStream.Seek(0, SeekOrigin.Begin);

        GifDecoder gifDecoder = new GifDecoder();
        int count = gifDecoder.GetFrameCount(InputStream);

        GifFrameCollection gifFrameCollection = new GifFrameCollection();
        gifFrameCollection.Height = OutputHeight;
        gifFrameCollection.Width = OutputWidth;

        double ratio;
        if (InputImage.Height > InputImage.Width)
        {
            ratio = (double)OutputHeight / (double)InputImage.Height;
        }
        else
        {
            ratio = (double)OutputWidth / (double)InputImage.Width;
        }

        for (int i = 0; i < count; i++)
        {
            GifFrame frame = gifDecoder.Frames[i];

            Rectangle rectangle = new Rectangle(Point.Empty, frame.Image.Size);

            int newframeWidth = frame.Image.Width;
            int newframeHeight = frame.Image.Height;
            if (newframeWidth > OutputWidth || newframeHeight > OutputHeight)
            {
                newframeWidth = (int)(frame.Image.Width * ratio);
                newframeHeight = (int)(frame.Image.Height * ratio);
            }

            // account for erratic rounding, seems illogical but has happened earlier when using floats instead of doubles 
            if (newframeWidth > OutputWidth)
            {
                newframeWidth = OutputWidth;
            }
            if (newframeHeight > OutputHeight)
            {
                newframeHeight = OutputHeight;
            }

            Size size = new Size(newframeWidth, newframeHeight);
            // only resize if we have a measureable dimension
            if (size.Width > 0 && size.Height > 0)
            {
                //ResampleCommand resampleCommand = new ResampleCommand(rectangle, size, ResampleMethod.);
                AtalaImage atalaImage = frame.Image;
                if (newframeWidth != frame.Image.Width || newframeHeight != frame.Image.Height)
                {
                    CropCommand command = new CropCommand(new Rectangle(new Point(0, 0), size));
                    atalaImage = command.Apply(frame.Image).Image;
                }
                // AtalaImage atalaImage = frame.Image;
                Point point = new Point((int)(frame.Location.X), (int)(frame.Location.Y));
                // Point point = new Point((int)(frame.Location.X), (int)(frame.Location.Y));
                gifFrameCollection.Add(new GifFrame(atalaImage, point, frame.DelayTime, frame.Interlaced, frame.FrameDisposal, frame.TransparentIndex, frame.UseLocalPalette));
            }
        }
        FileStream saveStream = new FileStream("resized.gif", FileMode.Create, FileAccess.Write, FileShare.Write);
        GifEncoder gifSave = new GifEncoder();
        gifSave.Save(saveStream, gifFrameCollection, null);
        saveStream.Close();
    }
滿滿的愛 2024-12-01 10:30:25

如果您使用不同的帧大小,则计算出的比率值不正确。您应该计算每个单独帧的比率,以便您关注的行使用正确的比率。我对框架不熟悉,所以无法给你提供准确的例子;但它应该看起来类似于:

GifFrame frame = gifDecoder.Frames[i];
double frameRatio;
if (frame.Height > frame.Width)
{
   frameRatio = (double)OutputHeight / (double)frame.Height;
}
else
{
   frameRatio = (double)OutputWidth / (double)frame.Width;
}

...

Point point = new Point((int)(frame.Location.X * frameRatio), (int)(frame.Location.Y * frameRatio));

The calculated ratio values are incorrect if you're working with different frame-sizes. You should calculate the ratio per individual frame, so that the line your concerned about uses the correct ratio. I'm not familiar with the framework, so I can't provide you with an accurate example; but it should look similar to this:

GifFrame frame = gifDecoder.Frames[i];
double frameRatio;
if (frame.Height > frame.Width)
{
   frameRatio = (double)OutputHeight / (double)frame.Height;
}
else
{
   frameRatio = (double)OutputWidth / (double)frame.Width;
}

...

Point point = new Point((int)(frame.Location.X * frameRatio), (int)(frame.Location.Y * frameRatio));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文