Esempio n. 1
0
        /// <summary>
        /// Converts the list of frames into an animated GIF, and writes it to the stream.
        /// </summary>
        /// <param name="stream">Output stream.</param>
        public void Save(Stream stream, out int maxWidth, out int maxHeight)
        {
            maxWidth = maxHeight = -1;

            if (Frames.Count == 0)
            {
                // nothing to do
                Debug.Assert(false);
                return;
            }

            //
            // Step 1: convert all BitmapFrame objects to GIF.  This lets the .NET GIF encoder
            // deal with the data compression.
            //
            List <UnpackedGif> gifs = new List <UnpackedGif>(Frames.Count);

            foreach (BitmapFrame bf in Frames)
            {
                GifBitmapEncoder encoder = new GifBitmapEncoder();
                encoder.Frames.Add(bf);
                using (MemoryStream ms = new MemoryStream()) {
                    encoder.Save(ms);
                    // We're using GetBuffer() rather than ToArray() to avoid a copy.  One
                    // consequence of this choice is that the byte[] may be oversized.  Since
                    // GIFs are treated as streams with explicit termination this should not
                    // pose a problem.
                    gifs.Add(UnpackedGif.Create(ms.GetBuffer()));
                }
            }

            //
            // Step 2: determine the size of the largest image.  This will become the logical
            // size of the animated GIF.
            //
            // TODO(maybe): We have an opportunity to replace all of the local color tables with a
            // single global color table.  This is only possible if all of the local tables are
            // identical and the transparency values in the GCE also match up.  (Well, it's
            // otherwise *possible*, but we'd need to decode, update palettes and pixels, and
            // re-encode.)
            //
            foreach (UnpackedGif gif in gifs)
            {
                //gif.DebugDump();

                if (maxWidth < gif.LogicalScreenWidth)
                {
                    maxWidth = gif.LogicalScreenWidth;
                }
                if (maxHeight < gif.LogicalScreenHeight)
                {
                    maxHeight = gif.LogicalScreenHeight;
                }
            }

            if (maxWidth < 0 || maxHeight < 0)
            {
                Debug.WriteLine("Unable to determine correct width/height");
                return;
            }

            //
            // Step 3: output data.
            //
            stream.Write(GIF89A_SIGNATURE, 0, GIF89A_SIGNATURE.Length);
            WriteLittleUshort(stream, (ushort)maxWidth);
            WriteLittleUshort(stream, (ushort)maxHeight);
            stream.WriteByte(0x70);         // no GCT; max color resolution (does this matter?)
            stream.WriteByte(0);            // BCI; not relevant
            stream.WriteByte(0);            // no aspect ratio adjustment

            stream.Write(NetscapeExtStart, 0, NetscapeExtStart.Length);
            stream.WriteByte(1);            // yes, we want to loop
            WriteLittleUshort(stream, 0);   // loop forever
            stream.WriteByte(0);            // end of block

            Debug.Assert(gifs.Count == FrameData.Count);
            for (int i = 0; i < Frames.Count; i++)
            {
                UnpackedGif gif = gifs[i];
                MetaData    md  = FrameData[i];

                // Just use the first image.
                UnpackedGif.GraphicRenderingBlock grb = gif.ImageBlocks[0];

                byte   colorTableSize;
                byte[] colorTable;
                if (grb.LocalColorTableFlag)
                {
                    colorTableSize = grb.LocalColorTableSize;
                    colorTable     = grb.LocalColorTable;
                }
                else if (gif.GlobalColorTableFlag)
                {
                    colorTableSize = gif.GlobalColorTableSize;
                    colorTable     = gif.GlobalColorTable;
                }
                else
                {
                    Debug.Assert(false);
                    colorTableSize = 0x07;
                    colorTable     = new byte[256 * 3]; // a whole lotta black
                }
                Debug.Assert(colorTable.Length == (1 << (colorTableSize + 1)) * 3);

                // If it has a GCE, use that.  Otherwise supply default values.  Either way
                // we use the frame delay from the meta-data.
                UnpackedGif.GraphicControlExtension gce = grb.GraphicControlExt;
                byte disposalMethod =
                    (byte)UnpackedGif.GraphicControlExtension.DisposalMethods.RestoreBackground;
                bool userInputFlag         = false;
                bool transparencyFlag      = false;
                byte transparentColorIndex = 0;
                if (gce != null)
                {
                    //disposalMethod = gce.DisposalMethod;
                    userInputFlag         = gce.UserInputFlag;
                    transparencyFlag      = gce.TransparencyFlag;
                    transparentColorIndex = gce.TransparentColorIndex;
                }

                stream.Write(GraphicControlStart, 0, GraphicControlStart.Length);
                stream.WriteByte((byte)((disposalMethod << 2) |
                                        (userInputFlag ? 0x02 : 0) | (transparencyFlag ? 0x01 : 0)));
                WriteLittleUshort(stream, (ushort)Math.Round(md.DelayMsec / 10.0));
                stream.WriteByte(transparentColorIndex);
                stream.WriteByte(0);            // end of GCE

                // Output image descriptor.  We can center the images in the animation or
                // just leave them in the top-left corner.
                stream.WriteByte(UnpackedGif.IMAGE_SEPARATOR);
                WriteLittleUshort(stream, 0);       // left
                WriteLittleUshort(stream, 0);       // top
                WriteLittleUshort(stream, gif.LogicalScreenWidth);
                WriteLittleUshort(stream, gif.LogicalScreenHeight);
                stream.WriteByte((byte)(0x80 | colorTableSize));    // local table, no sort/intrl

                // Local color table.
                stream.Write(colorTable, 0, colorTable.Length);

                // Image data.  Trailing $00 is included.
                stream.Write(grb.ImageData, grb.ImageStartOffset,
                             grb.ImageEndOffset - grb.ImageStartOffset + 1);
            }

            stream.WriteByte(UnpackedGif.GIF_TRAILER);
        }
Esempio n. 2
0
        /// <summary>
        /// Generate one or more GIF image files, and output references to them.
        /// </summary>
        /// <param name="offset">Visualization set file offset.</param>
        /// <param name="sb">String builder for the HTML output.</param>
        private void OutputVisualizationSet(int offset, StringBuilder sb)
        {
            const int IMAGE_SIZE         = 64;
            const int MAX_WIDTH_PER_LINE = 768;

            if (!mProject.VisualizationSets.TryGetValue(offset,
                                                        out VisualizationSet visSet))
            {
                sb.Append("Internal error - visualization set missing");
                Debug.Assert(false);
                return;
            }
            if (visSet.Count == 0)
            {
                sb.Append("Internal error - empty visualization set");
                Debug.Assert(false);
                return;
            }

            string imageDirFileName = Path.GetFileName(mImageDirPath);
            int    outputWidth      = 0;

            for (int index = 0; index < visSet.Count; index++)
            {
                string fileName = "vis" + offset.ToString("x6") + "_" + index.ToString("d2");

                int dispWidth, dispHeight;

                Visualization vis = visSet[index];
                if (vis is VisualizationAnimation)
                {
                    // Animated visualization.
                    VisualizationAnimation visAnim = (VisualizationAnimation)vis;
                    int frameDelay = PluginCommon.Util.GetFromObjDict(visAnim.VisGenParams,
                                                                      VisualizationAnimation.FRAME_DELAY_MSEC_PARAM, 330);
                    AnimatedGifEncoder encoder = new AnimatedGifEncoder();

                    // Gather list of frames.
                    for (int i = 0; i < visAnim.Count; i++)
                    {
                        Visualization avis = VisualizationSet.FindVisualizationBySerial(
                            mProject.VisualizationSets, visAnim[i]);
                        if (avis != null)
                        {
                            encoder.AddFrame(BitmapFrame.Create(avis.CachedImage), frameDelay);
                        }
                        else
                        {
                            Debug.Assert(false);        // not expected
                        }
                    }

#if false
                    // try feeding the animated GIF into our GIF unpacker
                    using (MemoryStream ms = new MemoryStream()) {
                        encoder.Save(ms);
                        Debug.WriteLine("TESTING");
                        UnpackedGif anim = UnpackedGif.Create(ms.GetBuffer());
                        anim.DebugDump();
                    }
#endif

                    // Create new or replace existing image file.
                    fileName += "_ani.gif";
                    string pathName = Path.Combine(mImageDirPath, fileName);
                    try {
                        using (FileStream stream = new FileStream(pathName, FileMode.Create)) {
                            encoder.Save(stream, out dispWidth, out dispHeight);
                        }
                    } catch (Exception ex) {
                        // TODO: add an error report
                        Debug.WriteLine("Error creating animated GIF file '" + pathName + "': " +
                                        ex.Message);
                        dispWidth = dispHeight = 1;
                    }
                }
                else
                {
                    // Bitmap visualization.
                    //
                    // Encode a GIF the same size as the original bitmap.
                    GifBitmapEncoder encoder = new GifBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(vis.CachedImage));

                    // Create new or replace existing image file.
                    fileName += ".gif";
                    string pathName = Path.Combine(mImageDirPath, fileName);
                    try {
                        using (FileStream stream = new FileStream(pathName, FileMode.Create)) {
                            encoder.Save(stream);
                        }
                    } catch (Exception ex) {
                        // Something went wrong with file creation.  We don't have an error
                        // reporting mechanism, so this will just appear as a broken or stale
                        // image reference.
                        // TODO: add an error report
                        Debug.WriteLine("Error creating GIF file '" + pathName + "': " +
                                        ex.Message);
                    }

                    dispWidth  = (int)vis.CachedImage.Width;
                    dispHeight = (int)vis.CachedImage.Height;
                }

                // Output thumbnail-size IMG element, preserving proportions.  I'm assuming
                // images will be small enough that generating a separate thumbnail would be
                // counter-productive.  This seems to look best if the height is consistent
                // across all visualization lines, but that can create some monsters (e.g.
                // a bitmap that's 1 pixel high and 40 wide), so we cap the width.
                int    dimMult = IMAGE_SIZE;
                double maxDim  = dispHeight;
                if (dispWidth > dispHeight * 2)
                {
                    // Too proportionally wide, so use the width as the limit.  Allow it to
                    // up to 2x the max width (which can't cause the thumb height to exceed
                    // the height limit).
                    maxDim   = dispWidth;
                    dimMult *= 2;
                }
                int thumbWidth  = (int)Math.Round(dimMult * (dispWidth / maxDim));
                int thumbHeight = (int)Math.Round(dimMult * (dispHeight / maxDim));
                //Debug.WriteLine(dispWidth + "x" + dispHeight + " --> " +
                //    thumbWidth + "x" + thumbHeight + " (" + maxDim + ")");

                if (outputWidth > MAX_WIDTH_PER_LINE)
                {
                    // Add a line break.  In "pre" mode the bitmaps just run off the right
                    // edge of the screen.  The way we're doing it is imprecise and doesn't
                    // flow with changes to the browser width, but it'll do for now.
                    sb.AppendLine("<br/>");
                    for (int i = 0; i < mColStart[(int)Col.Label]; i++)
                    {
                        sb.Append(' ');
                    }
                    outputWidth = 0;
                }
                else if (index != 0)
                {
                    sb.Append("&nbsp;");
                }
                outputWidth += thumbWidth;

                sb.Append("<img class=\"vis\" alt=\"vis\" src=\"");
                sb.Append(imageDirFileName);
                sb.Append('/');
                sb.Append(fileName);
                sb.Append("\" width=\"" + thumbWidth + "\" height=\"" + thumbHeight + "\"/>");
            }
        }
        private void SaveButton_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog fileDlg = new SaveFileDialog()
            {
                Filter        = Res.Strings.FILE_FILTER_GIF + "|" + Res.Strings.FILE_FILTER_ALL,
                FilterIndex   = 1,
                ValidateNames = true,
                AddExtension  = true,
                FileName      = mFileNameBase + ".gif"
            };

            if (fileDlg.ShowDialog() != true)
            {
                return;
            }
            string pathName = Path.GetFullPath(fileDlg.FileName);

            Debug.WriteLine("Save path: " + pathName);

            try {
                OutputSize item = (OutputSize)sizeComboBox.SelectedItem;
                if (mVis is VisWireframeAnimation)
                {
                    Debug.Assert(item.Width == item.Height);
                    AnimatedGifEncoder encoder = new AnimatedGifEncoder();
                    ((VisWireframeAnimation)mVis).EncodeGif(encoder, item.Width);

                    using (FileStream stream = new FileStream(pathName, FileMode.Create)) {
                        encoder.Save(stream, out int dispWidth, out int dispHeight);
                    }
                }
                else
                {
                    BitmapSource outImage;
                    if (IsBitmap)
                    {
                        int scale = item.Width / (int)mVis.CachedImage.Width;
                        Debug.Assert(scale >= 1);
                        if (scale == 1)
                        {
                            outImage = mVis.CachedImage;
                        }
                        else
                        {
                            outImage = mVis.CachedImage.CreateScaledCopy(scale);
                        }
                    }
                    else
                    {
                        Debug.Assert(item.Width == item.Height);
                        outImage = Visualization.GenerateWireframeImage(mWireObj,
                                                                        item.Width, mVis.VisGenParams);
                    }

                    GifBitmapEncoder encoder = new GifBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(outImage));

#if false
                    // try feeding the GIF into our GIF unpacker
                    using (MemoryStream ms = new MemoryStream()) {
                        encoder.Save(ms);
                        Debug.WriteLine("TESTING");
                        UnpackedGif anim = UnpackedGif.Create(ms.GetBuffer());
                        anim.DebugDump();
                    }
#else
                    using (FileStream stream = new FileStream(pathName, FileMode.Create)) {
                        encoder.Save(stream);
                    }
#endif
                }
            } catch (Exception ex) {
                // Error handling is a little sloppy, but this shouldn't fail often.
                MessageBox.Show(ex.Message, Res.Strings.ERR_FILE_GENERIC_CAPTION,
                                MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            // After successful save, close dialog box.
            DialogResult = true;
        }