Esempio n. 1
0
        private void SaveAsGif <T>(string file, Size size, Size maxSize, int frames, int frameDelay)
            where T : unmanaged, IPixel <T>
        {
            // Load full base image
            using Image <T> image = Image.LoadPixelData <T>(_ddsImageFile.Data, _ddsImageFile.Width, _ddsImageFile.Height);
            using Image <T> gif   = new Image <T>(size.Width, size.Height);

            for (int i = 0; i < frames; i++)
            {
#pragma warning disable SA1407 // Arithmetic expressions should declare precedence
                int xPos = i % (image.Width / maxSize.Width) * maxSize.Width;
#pragma warning restore SA1407 // Arithmetic expressions should declare precedence
                int yPos = i / (image.Width / maxSize.Width) * maxSize.Height;

                using Image <T> imagePart = image.Clone();

                imagePart.Mutate(x => x.Crop(new Rectangle(new Point(xPos, yPos), size)));

                GifFrameMetadata gifFrameMetadata = imagePart.Frames[0].Metadata.GetFormatMetadata(GifFormat.Instance);
                gifFrameMetadata.FrameDelay     = frameDelay / 10;
                gifFrameMetadata.DisposalMethod = GifDisposalMethod.RestoreToBackground;

                gif.Frames.AddFrame(imagePart.Frames[0]);
            }

            gif.Frames.RemoveFrame(0);
            gif.Save(file, new GifEncoder()
            {
                ColorTableMode = GifColorTableMode.Local,
            });
        }
            private static void CompareGifMetadata(ImageFrame a, ImageFrame b)
            {
                // TODO: all metadata classes should be equatable!
                GifFrameMetadata aData = a.Metadata.GetGifMetadata();
                GifFrameMetadata bData = b.Metadata.GetGifMetadata();

                Assert.Equal(aData.DisposalMethod, bData.DisposalMethod);
                Assert.Equal(aData.FrameDelay, bData.FrameDelay);
                Assert.Equal(aData.ColorTableLength, bData.ColorTableLength);
            }
        private static GifFrameMetadata GetGifFrameMetadata(BitmapFrame bitmapFrame)
        {
            var gifFrameMetadata = new GifFrameMetadata();

            if (bitmapFrame.Metadata is BitmapMetadata bitmapMetadata)
            {
                var delay = bitmapMetadata.GetQueryOrDefault <ushort>("/grctlext/Delay");
                gifFrameMetadata.Delay = TimeSpan.FromMilliseconds(delay * 10);
                var disposal = bitmapMetadata.GetQueryOrDefault <byte>("/grctlext/Disposal");
                gifFrameMetadata.Disposal = (FrameDisposalMethod)disposal;
                gifFrameMetadata.Left     = bitmapMetadata.GetQueryOrDefault <ushort>("/imgdesc/Left");
                gifFrameMetadata.Top      = bitmapMetadata.GetQueryOrDefault <ushort>("/imgdesc/Top");
                gifFrameMetadata.Width    = bitmapMetadata.GetQueryOrDefault <ushort>("/imgdesc/Width");
                gifFrameMetadata.Height   = bitmapMetadata.GetQueryOrDefault <ushort>("/imgdesc/Height");
            }

            return(gifFrameMetadata);
        }
Esempio n. 4
0
        public void NonMutatingEncodePreservesPaletteCount()
        {
            using (var inStream = new MemoryStream(TestFile.Create(TestImages.Gif.Leo).Bytes))
                using (var outStream = new MemoryStream())
                {
                    inStream.Position = 0;

                    var               image         = Image.Load <Rgba32>(inStream);
                    GifMetadata       metaData      = image.Metadata.GetGifMetadata();
                    GifFrameMetadata  frameMetadata = image.Frames.RootFrame.Metadata.GetGifMetadata();
                    GifColorTableMode colorMode     = metaData.ColorTableMode;
                    var               encoder       = new GifEncoder
                    {
                        ColorTableMode = colorMode,
                        Quantizer      = new OctreeQuantizer(new QuantizerOptions {
                            MaxColors = frameMetadata.ColorTableLength
                        })
                    };

                    image.Save(outStream, encoder);
                    outStream.Position = 0;

                    outStream.Position = 0;
                    var clone = Image.Load <Rgba32>(outStream);

                    GifMetadata cloneMetadata = clone.Metadata.GetGifMetadata();
                    Assert.Equal(metaData.ColorTableMode, cloneMetadata.ColorTableMode);

                    // Gifiddle and Cyotek GifInfo say this image has 64 colors.
                    Assert.Equal(64, frameMetadata.ColorTableLength);

                    for (int i = 0; i < image.Frames.Count; i++)
                    {
                        GifFrameMetadata ifm  = image.Frames[i].Metadata.GetGifMetadata();
                        GifFrameMetadata cifm = clone.Frames[i].Metadata.GetGifMetadata();

                        Assert.Equal(ifm.ColorTableLength, cifm.ColorTableLength);
                        Assert.Equal(ifm.FrameDelay, cifm.FrameDelay);
                    }

                    image.Dispose();
                    clone.Dispose();
                }
        }
        public void CloneIsDeep()
        {
            var meta = new GifFrameMetadata()
            {
                FrameDelay = 1,
                DisposalMethod = GifDisposalMethod.RestoreToBackground,
                ColorTableLength = 2
            };

            var clone = (GifFrameMetadata)meta.DeepClone();

            clone.FrameDelay = 2;
            clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious;
            clone.ColorTableLength = 1;

            Assert.False(meta.FrameDelay.Equals(clone.FrameDelay));
            Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod));
            Assert.False(meta.ColorTableLength.Equals(clone.ColorTableLength));
        }
Esempio n. 6
0
        public void ConstructorImageFrameMetadata()
        {
            const int frameDelay                   = 42;
            const int colorTableLength             = 128;
            const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground;

            var metaData = new ImageFrameMetadata();
            GifFrameMetadata gifFrameMetadata = metaData.GetGifMetadata();

            gifFrameMetadata.FrameDelay       = frameDelay;
            gifFrameMetadata.ColorTableLength = colorTableLength;
            gifFrameMetadata.DisposalMethod   = disposalMethod;

            var clone = new ImageFrameMetadata(metaData);
            GifFrameMetadata cloneGifFrameMetadata = clone.GetGifMetadata();

            Assert.Equal(frameDelay, cloneGifFrameMetadata.FrameDelay);
            Assert.Equal(colorTableLength, cloneGifFrameMetadata.ColorTableLength);
            Assert.Equal(disposalMethod, cloneGifFrameMetadata.DisposalMethod);
        }
Esempio n. 7
0
        private static void ConvertGifToMode2(string sourceFile, string outputPath)
        {
            string newPath = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(sourceFile));

            if (File.Exists(newPath))
            {
                return;
            }

            DateTime modificationTime = File.GetLastWriteTimeUtc(sourceFile);

            Image <Rgba32> gif = Image.Load <Rgba32>(sourceFile);

            int height     = 256;
            int width      = 160;
            int frameCount = gif.Frames.Count;

            // Currently use ints to hold a list of pixel colours for a pixel.  Each takes three bits,
            // so can only do 10.  Easily extended with a long though.
            Debug.Assert(frameCount <= 10);
            if (frameCount > 10)
            {
                throw new ApplicationException("The source image has more than 10 frames");
            }

            // These contains every observed pattern of colour cycling
            HashSet <int> staticCycles = new HashSet <int>(); // Cycles where the colour doesn't change
            HashSet <int> colourCycles = new HashSet <int>(); // Cycles where the colour changes

            byte[, ,] frames = new byte[frameCount, width, height];

            int  frameIndex   = 0;
            bool patchWarning = false;

            foreach (ImageFrame <Rgba32> frame in gif.Frames)
            {
                Debug.Assert(frame.Width == width * 4);
                Debug.Assert(frame.Height == height * 2);
                if (frame.Width != width * 4)
                {
                    throw new ApplicationException(string.Format("The source image is not {0} pixels wide", width * 4));
                }
                if (frame.Height != height * 2)
                {
                    throw new ApplicationException(string.Format("The source image is not {0} pixels high", height * 2));
                }

                for (int y = 0; y != height; ++y)
                {
                    for (int x = 0; x != width; ++x)
                    {
                        int   xs = 4 * x;
                        int   ys = 2 * y;
                        int   r  = 0;
                        int   g  = 0;
                        int   b  = 0;
                        Color c  = frame[xs, ys];
                        for (int y0 = 0; y0 != 2; ++y0)
                        {
                            for (int x0 = 0; x0 != 4; ++x0)
                            {
                                Color  c2 = frame[xs + x0, ys + y0];
                                Rgba32 p  = c2.ToPixel <Rgba32>();
                                Debug.Assert(c == c2);
                                if (c2 != c)
                                {
                                    patchWarning = true;
                                }
                                r += p.R;
                                g += p.G;
                                b += p.B;
                            }
                        }
                        frames[frameIndex, x, y] = (byte)BeebColour(8 * 128, r, g, b);
                    }
                }
                ++frameIndex;
            }
            if (patchWarning)
            {
                Console.WriteLine("img2beeb: WARNING: A 2x4 patch contains varying colours; is this a MODE2 image?");
            }
            // Calculate vector of pixel colours across frames;
            // remember each unique vector.
            for (int y = 0; y != height; ++y)
            {
                for (int x = 0; x != width; ++x)
                {
                    byte colour = frames[0, x, y];
                    int  vector = colour;
                    bool match  = true;
                    for (int frame = 1; frame != frameCount; ++frame)
                    {
                        byte current = frames[frame, x, y];
                        vector = (vector << 3) | current;
                        match  = match && (colour == current);
                    }
                    if (match)
                    {
                        staticCycles.Add(vector);
                    }
                    else
                    {
                        colourCycles.Add(vector);
                    }
                }
            }

            Debug.Assert(staticCycles.Count + colourCycles.Count <= 16);
            if (staticCycles.Count + colourCycles.Count > 16)
            {
                throw new ApplicationException("The animation requires more than 16 colours");
            }

            // These indices could be assigned on a single pass through the data,
            // but doing it at the end means they can be sorted.

            List <int> colourCyclesSorted = colourCycles.ToList();

            colourCyclesSorted.Sort();
            List <int> staticCyclesSorted = staticCycles.ToList();

            staticCyclesSorted.Sort();

            Dictionary <int, byte> colourNumbers = new Dictionary <int, byte>();

            foreach (int cycle in staticCyclesSorted)
            {
                colourNumbers.Add(cycle, (byte)(colourNumbers.Count));
            }
            foreach (int cycle in colourCyclesSorted)
            {
                colourNumbers.Add(cycle, (byte)(colourNumbers.Count));
            }

            byte[,] final = new byte[width, height];
            for (int y = 0; y != height; ++y)
            {
                for (int x = 0; x != width; ++x)
                {
                    // Create vector of observed colours across frames
                    int vector = 0;
                    for (int frame = 0; frame != frameCount; ++frame)
                    {
                        vector = (vector << 3) | frames[frame, x, y];
                    }
                    final[x, y] = colourNumbers[vector];
                }
            }

            int rowCount  = (height + 7) / 8;
            int rowLength = 640;

            byte[] mode2 = new byte[rowLength * rowCount];

            for (int y = 0; y != height; ++y)
            {
                int rowStart = (y / 8) * rowLength + (y % 8);
                for (int x = 0; x != width; ++x)
                {
                    // Source pixels are doubled up so each byte has two pixels the same colour
                    int sourcePixel = final[x, y];
                    Debug.Assert(sourcePixel < 16);
                    byte mask      = (x % 2) == 0 ? (byte)0xAA : (byte)0x55;
                    byte colour    = (byte)(mode2ColourTable[sourcePixel] & mask);
                    int  destIndex = rowStart + (x / 2) * 8;
                    mode2[destIndex] |= colour;
                }
            }

            byte[] palette = new byte[256];
            byte[] message = Encoding.ASCII.GetBytes("Mode2 Animation");
            message.CopyTo(palette, 0);

            int index = message.Length + 1;

            palette[index++] = (byte)frameCount;
            palette[index++] = (byte)staticCycles.Count;
            palette[index++] = (byte)colourCycles.Count;
            for (int colour = 0; colour != staticCyclesSorted.Count; ++colour)
            {
                palette[index++] = (byte)(staticCyclesSorted[colour] & 0x07);
            }
            for (int frame = 0; frame != frameCount; ++frame)
            {
                for (int colour = 0; colour != colourCyclesSorted.Count; ++colour)
                {
                    palette[index++] = (byte)((colourCyclesSorted[colour] >> (3 * (frameCount - frame - 1))) & 0x07);
                }
                // Get frame delay in centiseconds
                GifFrameMetadata fmd = gif.Frames[frame].Metadata.GetFormatMetadata(GifFormat.Instance);
                int frameDelay       = fmd.FrameDelay;
                Debug.Assert(frameDelay >= 0 && frameDelay < 256);
                if (frameDelay >= 256)
                {
                    throw new ApplicationException("The frame delay is too long!");
                }
                palette[index++] = (byte)frameDelay;
            }

            using (FileStream fs = new FileStream(newPath, FileMode.CreateNew, FileAccess.Write))
            {
                fs.Write(palette, 0, palette.Length);
                fs.Write(mode2, 0, mode2.Length);
            }
            File.SetLastWriteTimeUtc(newPath, modificationTime);
        }
Esempio n. 8
0
        private static void GifDecodingCompleteCallback(int taskId)
        {
            if (!DecodeTasks.ContainsKey(taskId) || DecodeTasks[taskId] == null)
            {
                Debug.LogErrorFormat("Complete decoding GIF image with invalid task ID {0}. Please check!", taskId);
                return;
            }

            var         taskResources = DecodeTasks[taskId];
            GifMetadata gifMetadata;

            GifFrameMetadata[] gifFrameMetadata = null;
            Color32[][]        imageData        = null;

            try
            {
                gifMetadata = (GifMetadata)taskResources.gifMetadataHandle.Target;

                if (taskResources.frameMetadataHandles != null || taskResources.imageDataHandles != null)
                {
                    imageData        = new Color32[taskResources.imageDataHandles.Length][];
                    gifFrameMetadata = new GifFrameMetadata[taskResources.frameMetadataHandles.Length];

                    for (int i = 0; i < taskResources.frameMetadataHandles.Length; i++)
                    {
                        gifFrameMetadata[i] = (GifFrameMetadata)taskResources.frameMetadataHandles[i].Target;
                    }

                    for (int j = 0; j < taskResources.imageDataHandles.Length; j++)
                    {
                        imageData[j] = (Color32[])taskResources.imageDataHandles[j].Target;
                    }
                }

                if (taskResources.completeCallback != null)
                {
                    var callback = taskResources.completeCallback;  // cache the callback reference as we'll reset taskResources soon.
                    RuntimeHelper.RunOnMainThread(() =>
                    {
                        callback(taskId, gifMetadata, gifFrameMetadata, imageData);
                    });
                }
            }
            catch (System.InvalidCastException e)
            {
                Debug.LogError("Error casting GCHandle back to decoding buffer:" + e);
                throw e;
            }
            finally
            {
                taskResources.gifMetadataHandle.Free();

                if (taskResources.frameMetadataHandles != null)
                {
                    foreach (var handle in taskResources.frameMetadataHandles)
                    {
                        handle.Free();
                    }
                }

                if (taskResources.imageDataHandles != null)
                {
                    foreach (var handle in taskResources.imageDataHandles)
                    {
                        handle.Free();
                    }
                }

                taskResources = null;
                DecodeTasks.Remove(taskId);
            }
        }
Esempio n. 9
0
 /// <summary>
 /// Initializes a new instance of the <see cref="GifFrameMetadata"/> class.
 /// </summary>
 /// <param name="other">The metadata to create an instance from.</param>
 private GifFrameMetadata(GifFrameMetadata other)
 {
     this.ColorTableLength = other.ColorTableLength;
     this.FrameDelay       = other.FrameDelay;
     this.DisposalMethod   = other.DisposalMethod;
 }
Esempio n. 10
0
        public RomInfo(string decPath, bool parseImages = false)
        {
            byte[] contentBytes = File.ReadAllBytes(decPath);

            ushort headerChecksum   = CalcCrc16Modbus(contentBytes.Slice(0, HeaderChecksumOffset));
            ushort romChecksumValue = BitConverter.ToUInt16(contentBytes.Slice(HeaderChecksumOffset, 2, true).Reverse().ToArray());

            ValidContent = headerChecksum == romChecksumValue;
            if (!ValidContent)
            {
                return;
            }

            GameTitle = new string(contentBytes.Slice(GameTitleOffset, GameTitleSize).AsChars()).TrimEnd('\0');
            GameCode  = new string(contentBytes.Slice(GameCodeOffset, GameCodeSize).AsChars());

            if (GameCode.Length >= GameCodeLength)
            {
                RegionCode = GameCode[GameCodeLength - 1];
            }
            else
            {
                Log.Instance.Warn($"The gamecode for '{decPath}' is not valid: '{GameCode}'");
            }


            int titleInfoAddress = BitConverter.ToInt32(contentBytes.Slice(TitleInfoAddressOffset, TitleInfoAddressSize, true).Reverse().ToArray());

            if (titleInfoAddress == 0x00000000)
            {
                return;
            }

            TitleInfoVersion = (TitleInfoVersions)BitConverter.ToInt16(contentBytes.Slice(titleInfoAddress, 2, true).Reverse().ToArray());

            UnicodeEncoding encoding   = new UnicodeEncoding(false, false);
            int             titleCount = TitleInfoVersion == TitleInfoVersions.Original ? 6 : TitleInfoVersion == TitleInfoVersions.ChineseTitle ? 7 : 8;

            for (int i = 0; i < titleCount; i++)
            {
                Titles[i] = encoding.GetString(contentBytes.Slice(titleInfoAddress + TitleInfoTitlesOffset + TitleInfoTitleSize * i, TitleInfoTitleSize)).Trim('\0', '\uffff').AsNullIfEmpty();
            }

            if (!parseImages)
            {
                return;
            }

            // Static icon
            StaticIcon = BuildImage(ParseBitmapIndices(contentBytes.Slice(titleInfoAddress + TitleInfoStaticIconOffset, IconBitmapSize)),
                                    ParsePalette(contentBytes.Slice(titleInfoAddress + TitleInfoStaticIconOffset + IconBitmapSize, IconPaletteBytesPerColour * IconPaletteColours)));

            PngMetadata pngMetadata = StaticIcon.Metadata.GetFormatMetadata(PngFormat.Instance);

            pngMetadata.ColorType = PngColorType.Palette;
            pngMetadata.TextData  = new List <PngTextData> {
                new PngTextData("description", $"Icon for the DSiWare title \"{GetFriendlyTitle()}\" (game code \"{GameCode}\").", "en-ca", "")
            };

            // Animated icon
            if (TitleInfoVersion != TitleInfoVersions.ChineseKoreanTitlesAnimated)
            {
                return;
            }

            byte[] sequenceBytes = contentBytes.Slice(titleInfoAddress + TitleInfoAnimIconSequenceOffset, TitleInfoAnimIconSequenceFrameCount * 2);

            // Non-animated icons start with 0x01000001
            if (sequenceBytes[0] == 0x01 && sequenceBytes[1] == 0x00 && sequenceBytes[2] == 0x00 && sequenceBytes[3] == 0x01)
            {
                return;
            }

            byte[][] bitmapPaletteIndices = new byte[TitleInfoAnimIconBitmapCount][];
            for (int i = 0; i < TitleInfoAnimIconBitmapCount; i++)
            {
                bitmapPaletteIndices[i] = ParseBitmapIndices(contentBytes.Slice(
                                                                 titleInfoAddress + TitleInfoAnimIconBitmapsOffset + i * IconBitmapSize,
                                                                 IconBitmapSize));
            }

            Rgba32[][] paletteColours = new Rgba32[TitleInfoAnimIconPaletteCount][];
            for (int i = 0; i < TitleInfoAnimIconPaletteCount; i++)
            {
                paletteColours[i] = ParsePalette(contentBytes.Slice(
                                                     titleInfoAddress + TitleInfoAnimIconPalettesOffset + i * IconPaletteBytesPerColour * IconPaletteColours,
                                                     IconPaletteBytesPerColour * IconPaletteColours));
            }

            AnimatedIcon = new Image <Rgba32>(IconSquareDim, IconSquareDim);
            for (int i = 0; i < TitleInfoAnimIconSequenceFrameCount; i++)
            {
                if (sequenceBytes[i * 2] == 0x00 && sequenceBytes[i * 2 + 1] == 0x00)
                {
                    break;
                }

                bool flipVertical   = (sequenceBytes[i * 2 + 1] & 0b10000000) >> 7 == 1;
                bool flipHorizontal = (sequenceBytes[i * 2 + 1] & 0b01000000) >> 6 == 1;
                byte paletteIndex   = (byte)((sequenceBytes[i * 2 + 1] & 0b00111000) >> 3);
                byte bitmapIndex    = (byte)(sequenceBytes[i * 2 + 1] & 0b00000111);
                byte frameDuration  = sequenceBytes[i * 2];                 // in 60Hz units

                Image <Rgba32> frame = BuildImage(bitmapPaletteIndices[bitmapIndex], paletteColours[paletteIndex], flipHorizontal, flipVertical);

                ImageFrame <Rgba32> aFrame        = AnimatedIcon.Frames.AddFrame(frame.Frames.RootFrame);
                GifFrameMetadata    frameMetadata = aFrame.Metadata.GetFormatMetadata(GifFormat.Instance);
                frameMetadata.FrameDelay     = (int)Math.Round(frameDuration * FrameDurationConversion);
                frameMetadata.DisposalMethod = GifDisposalMethod.RestoreToBackground;
            }
            AnimatedIcon.Frames.RemoveFrame(0);
            AnimatedIcon.Metadata.GetFormatMetadata(GifFormat.Instance).RepeatCount = 0;
            GifMetadata gifMetadata = AnimatedIcon.Metadata.GetFormatMetadata(GifFormat.Instance);

            gifMetadata.RepeatCount = 0;
            gifMetadata.Comments    = new List <string> {
                $"Icon for the DSiWare title \"{GetFriendlyTitle()}\" (game code \"{GameCode}\")."
            };
        }