private static HgxImage ExtractHg2Internal(HGXHDR hdr, BinaryReader reader, string fileName, string outputDir,
                                                   HgxOptions options)
        {
            Stream stream = reader.BaseStream;

            List <Hg2FrameInfo> frameInfos = new List <Hg2FrameInfo>();
            Hg2FrameInfo        frameInfo  = null;
            long startPosition             = stream.Position;

            do
            {
                stream.Position = startPosition + (frameInfo?.Img.OffsetNext ?? 0);
                startPosition   = stream.Position;

                frameInfo = ReadHg2FrameInfo(reader, hdr, false);
                frameInfos.Add(frameInfo);
            } while (frameInfo.Img.OffsetNext != 0);

            HgxImage hg2Image = new HgxImage(Path.GetFileName(fileName), hdr, frameInfos.ToArray(), options);

            if (outputDir != null)
            {
                for (int imgIndex = 0; imgIndex < frameInfos.Count; imgIndex++)
                {
                    frameInfo = frameInfos[imgIndex];
                    string pngFile = hg2Image.Frames[imgIndex].GetFrameFilePath(outputDir, false);
                    ExtractHg2Image(reader, frameInfo, options, pngFile);
                }
            }

            return(hg2Image);
        }
        /// <summary>
        ///  Writes the bitmap buffer to <paramref name="pngFile"/> and optional performs expansion if
        ///  <paramref name="expand"/> is true.
        /// </summary>
        /// <param name="buffer">The buffer to the image bits.</param>
        /// <param name="std">The HG3STDINFO containing image dimensions, etc.</param>
        /// <param name="expand">True if the image should be expanded to its full size.</param>
        /// <param name="pngFile">The path to the file to save to.</param>
        private static void WriteJpegToPng(byte[] buffer, HG3STDINFO std, HgxOptions options, string pngFile)
        {
            int         depthBytes   = (std.DepthBits + 7) / 8;
            int         stride       = (std.Width * depthBytes + 3) & ~3;
            PixelFormat expandFormat = PixelFormat.Format32bppArgb;
            // Do expansion here, and up to 32 bits if not 32 bits already.
            bool expand = options.HasFlag(HgxOptions.Expand);

            if (expand && (std.Width != std.TotalWidth || std.Height != std.TotalHeight))
            {
                using (var ms = new MemoryStream(buffer))
                    using (var jpeg = (Bitmap)Image.FromStream(ms))
                        using (var bitmapExpand = new Bitmap(std.TotalWidth, std.TotalHeight, expandFormat))
                            using (Graphics g = Graphics.FromImage(bitmapExpand)) {
                                if (options.HasFlag(HgxOptions.Flip))
                                {
                                    jpeg.RotateFlip(RotateFlipType.RotateNoneFlipY);
                                }
                                g.DrawImageUnscaled(jpeg, std.OffsetX, std.OffsetY);
                                bitmapExpand.Save(pngFile, ImageFormat.Png);
                            }
            }
            else
            {
                using (var ms = new MemoryStream(buffer))
                    using (var jpeg = (Bitmap)Image.FromStream(ms)) {
                        if (options.HasFlag(HgxOptions.Flip))
                        {
                            jpeg.RotateFlip(RotateFlipType.RotateNoneFlipY);
                        }
                        jpeg.Save(pngFile, ImageFormat.Png);
                    }
            }
        }
        /// <summary>
        ///  Writes the bitmap buffer to <paramref name="pngFile"/> and optional performs expansion if
        ///  <paramref name="expand"/> is true.
        /// </summary>
        /// <param name="buffer">The buffer to the image bits.</param>
        /// <param name="std">The HG3STDINFO containing image dimensions, etc.</param>
        /// <param name="expand">True if the image should be expanded to its full size.</param>
        /// <param name="pngFile">The path to the file to save to.</param>
        private static void WritePng(byte[] buffer, HG2IMG std, HgxOptions options, string pngFile)
        {
            GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

            try {
                IntPtr      scan0 = handle.AddrOfPinnedObject();
                int         depthBytes = (std.DepthBits + 7) / 8;
                int         stride = (std.Width * depthBytes + 3) & ~3;
                PixelFormat format, expandFormat = PixelFormat.Format32bppArgb;
                switch (std.DepthBits)
                {
                case 24: format = PixelFormat.Format24bppRgb; break;

                case 32: format = PixelFormat.Format32bppArgb; break;

                default: throw new HgxException($"Unsupported depth bits {std.DepthBits}!");
                }
                // Do expansion here, and up to 32 bits if not 32 bits already.
                bool expand = options.HasFlag(HgxOptions.Expand);
                if (expand && (std.Width != std.TotalWidth || std.Height != std.TotalHeight))
                {
                    using (var bitmap = new Bitmap(std.Width, std.Height, stride, format, scan0))
                        using (var bitmapExpand = new Bitmap(std.TotalWidth, std.TotalHeight, expandFormat))
                            using (Graphics g = Graphics.FromImage(bitmapExpand)) {
                                if (options.HasFlag(HgxOptions.Flip))
                                {
                                    bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
                                }
                                g.DrawImageUnscaled(bitmap, std.OffsetX, std.OffsetY);
                                bitmapExpand.Save(pngFile, ImageFormat.Png);
                            }
                }
                else
                {
                    using (var bitmap = new Bitmap(std.Width, std.Height, stride, format, scan0)) {
                        if (options.HasFlag(HgxOptions.Flip))
                        {
                            bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
                        }
                        bitmap.Save(pngFile, ImageFormat.Png);
                    }
                }
            } finally {
                // Thing to note that gave me headaches earlier:
                //  Once this handle is freed, the bitmap loaded from
                //  scan0 will be invalidated after garbage collection.
                handle.Free();
            }
        }
        private static HgxImage ExtractHg3Internal(HGXHDR hdr, BinaryReader reader, string fileName, string outputDir,
                                                   HgxOptions options)
        {
            Stream stream = reader.BaseStream;

            List <Hg3FrameInfo> frameInfos = new List <Hg3FrameInfo>();
            Hg3FrameInfo        frameInfo  = null;
            long startPosition             = stream.Position;

            do
            {
                stream.Position = startPosition + (frameInfo?.Header.OffsetNext ?? 0);

                // NEW-NEW METHOD: We now know the next offset ahead
                // of time from the HG3OFFSET we're going to read.
                // Usually skips 0 bytes, otherwise usually 1-7 bytes.
                startPosition = stream.Position;

                frameInfo = ReadHg3FrameInfo(reader, hdr, false);
                frameInfos.Add(frameInfo);
            } while (frameInfo.Header.OffsetNext != 0);

            HgxImage hg3Image = new HgxImage(Path.GetFileName(fileName), hdr, frameInfos.ToArray(), options);

            if (outputDir != null)
            {
                for (int imgIndex = 0; imgIndex < frameInfos.Count; imgIndex++)
                {
                    frameInfo = frameInfos[imgIndex];
                    string pngFile = hg3Image.Frames[imgIndex].GetFrameFilePath(outputDir, false);
                    ExtractHg3Image(reader, frameInfo, options, pngFile);
                }
            }

            return(hg3Image);
        }
 /// <summary>
 ///  Extracts the HG-3 image information and images contained within HG-3 file to the output
 ///  <paramref name="outputDir"/>.
 /// </summary>
 /// <param name="hg3File">The file path to the HG-3 file.</param>
 /// <param name="outputDir">The output directory to save the images to.</param>
 /// <param name="options">The options for manipulating the image during extraction.</param>
 /// <returns>The extracted <see cref="HgxImage"/> information.</returns>
 ///
 /// <exception cref="ArgumentNullException">
 ///  <paramref name="hg3File"/> or <paramref name="outputDir"/> is null.
 /// </exception>
 public static HgxImage ExtractImages(string hg3File, string outputDir, HgxOptions options)
 {
     using (var stream = File.OpenRead(hg3File))
         return(ExtractInternal(stream, hg3File, outputDir, options));
 }
 /// <summary>
 ///  Extracts the HG-X image information from the KIFINT entry's open KIFINT archive stream and saves all
 ///  images to the output <paramref name="outputDir"/>.
 /// </summary>
 /// <param name="entry">The entry to extract from.</param>
 /// <param name="kifintStream">The stream to the open KIFINT archive.</param>
 /// <param name="outputDir">The output directory to save the images to.</param>
 /// <param name="options">The options for manipulating the image during extraction.</param>
 /// <returns>The extracted <see cref="HgxImage"/> information.</returns>
 ///
 /// <exception cref="ArgumentNullException">
 ///  <paramref name="entry"/>, <paramref name="kifintStream"/>, or <paramref name="outputDir"/> is null.
 /// </exception>
 public static HgxImage ExtractHgxAndImages(this KifintEntry entry, KifintStream kifintStream, string outputDir, HgxOptions options)
 {
     if (entry == null)
     {
         throw new ArgumentNullException(nameof(entry));
     }
     using (var stream = entry.ExtractToStream(kifintStream))
         return(HgxImage.ExtractImages(stream, entry.FileName, outputDir, options));
 }
        private static void ExtractHg3ImageJpegAlpha(BinaryReader reader, Hg3FrameInfo frameInfo, HgxOptions options,
                                                     string pngFile)
        {
            HG3STDINFO std = frameInfo.StdInfo;
            HG3IMG_AL  img = frameInfo.ImgAl.Data;
            HG3TAG     tag = frameInfo.ImgJpg.Tag;

            reader.BaseStream.Position = frameInfo.ImgAl.Offset;

            // WGC handles bad alpha buffer errors gracefully, so we should too
            byte[] alphaBuffer;
            try {
                alphaBuffer = Zlib.Decompress(reader, img.CompressedLength, img.DecompressedLength);
            } catch (ZlibException ex) {
                if (ex.Result == ZResult.DataError || ex.Result == ZResult.BufferError)
                {
                    alphaBuffer = ex.OutputBuffer;
                }
                else
                {
                    throw;
                }
            }

            reader.BaseStream.Position = frameInfo.ImgJpg.Offset;

            byte[] buffer = reader.ReadBytes(tag.Length);

            if (!CatDebug.SpeedTestHgx)
            {
                WriteJpegAlphaMaskToPng(buffer, alphaBuffer, std, options, pngFile);
            }
        }
        private static void ExtractHg3ImageAlpha(BinaryReader reader, Hg3FrameInfo frameInfo, HgxOptions options,
                                                 string pngFile)
        {
            HG3STDINFO std = frameInfo.StdInfo;
            HG3IMG_AL  img = frameInfo.ImgAl.Data;

            reader.BaseStream.Position = frameInfo.ImgAl.Offset;

            // WGC handles bad alpha buffer errors gracefully, so we should too
            byte[] alphaBuffer;
            try {
                alphaBuffer = Zlib.Decompress(reader, img.CompressedLength, img.DecompressedLength);
            } catch (ZlibException ex) {
                if (ex.Result == ZResult.DataError || ex.Result == ZResult.BufferError)
                {
                    alphaBuffer = ex.OutputBuffer;
                }
                else
                {
                    throw;
                }
            }

            int depthBytes  = (std.DepthBits + 7) / 8;
            int stride      = (std.Width * depthBytes + 3) & ~3;
            int minStride   = (std.Width * depthBytes);
            int alphaStride = std.Width;

            byte[] pixelBuffer = new byte[stride * std.Height];
            for (int y = 0; y < std.Height; y++)
            {
                int src = y * alphaStride;
                int dst = y * stride;
                for (int x = 0; x < std.Width; x++)
                {
                    int  alphaIndex = src + x;
                    int  pixelIndex = dst + x * depthBytes;
                    byte alpha      = unchecked ((byte)(byte.MaxValue - alphaBuffer[alphaIndex]));
                    pixelBuffer[pixelIndex + 0] = alpha;
                    pixelBuffer[pixelIndex + 1] = alpha;
                    pixelBuffer[pixelIndex + 2] = alpha;
                    if (depthBytes == 4)
                    {
                        pixelBuffer[pixelIndex + 3] = byte.MaxValue;
                    }
                }
            }

            if (!CatDebug.SpeedTestHgx)
            {
                WritePng(pixelBuffer, std, options, pngFile);
            }
        }
        private static void ExtractHg3ImageJpeg(BinaryReader reader, Hg3FrameInfo frameInfo, HgxOptions options,
                                                string pngFile)
        {
            HG3STDINFO std = frameInfo.StdInfo;
            HG3TAG     tag = frameInfo.ImgJpg.Tag;

            reader.BaseStream.Position = frameInfo.ImgJpg.Offset;

            byte[] buffer = reader.ReadBytes(tag.Length);

            if (!CatDebug.SpeedTestHgx)
            {
                WriteJpegToPng(buffer, std, options, pngFile);
            }
        }
        /// <summary>
        ///  Extracts the <see cref="HG3IMG"/> from the HG-3 file.
        /// </summary>
        /// <param name="reader">The binary reader for the file.</param>
        /// <param name="std">The HG3STDINFO containing image dimensions, etc.</param>
        /// <param name="img">The image header used to process the image.</param>
        /// <param name="expand">True if the image should be expanded to its full size.</param>
        /// <param name="pngFile">The path to the PNG file to save to.</param>
        private static void ExtractHg3ImageStandard(BinaryReader reader, Hg3FrameInfo frameInfo, HgxOptions options,
                                                    string pngFile)
        {
            HG3STDINFO std = frameInfo.StdInfo;
            HG3IMG     img = frameInfo.Img.Data;

            reader.BaseStream.Position = frameInfo.Img.Offset;

            byte[] pixelBuffer = ProcessImage(reader, std.Width, std.Height, std.DepthBits, img.Data);

            if (!CatDebug.SpeedTestHgx)
            {
                // This image type is normally flipped, so reverse the option
                options ^= HgxOptions.Flip;
                WritePng(pixelBuffer, std, options, pngFile);
            }
        }
        private static void ExtractHg3Image(BinaryReader reader, Hg3FrameInfo frameInfo, HgxOptions options,
                                            string pngFile)
        {
            switch (frameInfo.Type)
            {
            case Hg3ImageType.Image:
                ExtractHg3ImageStandard(reader, frameInfo, options, pngFile);
                break;

            case Hg3ImageType.Jpeg:
                ExtractHg3ImageJpeg(reader, frameInfo, options, pngFile);
                break;

            case Hg3ImageType.Alpha:
                ExtractHg3ImageAlpha(reader, frameInfo, options, pngFile);
                break;

            case Hg3ImageType.JpegAlpha:
                ExtractHg3ImageJpegAlpha(reader, frameInfo, options, pngFile);
                break;

            default: throw new InvalidOperationException("No HG-X image tags found!");
            }
        }
        private static void ExtractHg3ImageFromFrame(Stream stream, HgxFrame frame, string pngFile, HgxOptions options)
        {
            BinaryReader reader = new BinaryReader(stream);

            stream.Position = frame.FrameOffset;

            HGXHDR hdr = new HGXHDR {
                Type = frame.HgxImage.Type
            };
            Hg3FrameInfo frameInfo = ReadHg3FrameInfo(reader, hdr, true);

            ExtractHg3Image(reader, frameInfo, options, pngFile);
        }
        /// <summary>
        ///  Extracts the <see cref="HG2IMG"/> from the HG-2 file.
        /// </summary>
        /// <param name="reader">The binary reader for the file.</param>
        /// <param name="std">The HG3STDINFO containing image dimensions, etc.</param>
        /// <param name="img">The image header used to process the image.</param>
        /// <param name="expand">True if the image should be expanded to its full size.</param>
        /// <param name="pngFile">The path to the PNG file to save to.</param>
        private static void ExtractHg2Image(BinaryReader reader, Hg2FrameInfo frameInfo, HgxOptions options,
                                            string pngFile)
        {
            HG2IMG img = frameInfo.Img;

            reader.BaseStream.Position = frameInfo.Offset;
            int depthBytes = (img.DepthBits + 7) / 8;
            int stride     = (img.Width * depthBytes + 3) & ~3;

            byte[] pixelBuffer = ProcessImage(reader, img.Width, img.Height, img.DepthBits, img.Data);

            if (!CatDebug.SpeedTestHgx)
            {
                // This image type is normally flipped, so reverse the option
                options ^= HgxOptions.Flip;
                WritePng(pixelBuffer, img, options, pngFile);
            }
        }
        /// <summary>
        ///  Writes the bitmap buffer to <paramref name="pngFile"/> and optional performs expansion if
        ///  <paramref name="expand"/> is true.
        /// </summary>
        /// <param name="buffer">The buffer to the image bits.</param>
        /// <param name="std">The HG3STDINFO containing image dimensions, etc.</param>
        /// <param name="expand">True if the image should be expanded to its full size.</param>
        /// <param name="pngFile">The path to the file to save to.</param>
        private static unsafe void WriteJpegAlphaMaskToPng(byte[] buffer, byte[] alpha, HG3STDINFO std,
                                                           HgxOptions options, string pngFile)
        {
            bool expand = options.HasFlag(HgxOptions.Expand);

            expand = expand && (std.Width != std.TotalWidth || std.Height != std.TotalHeight);
            int offsetX = (expand ? std.OffsetX : 0);
            int offsetY = (expand ? std.OffsetY : 0);
            int width   = (expand ? std.TotalWidth  : std.Width);
            int height  = (expand ? std.TotalHeight : std.Height);

            int        depthBytes = 4;
            int        jpgStride = std.Width * depthBytes;
            int        stride = width * depthBytes;
            int        bufferSize = height * stride;
            int        alphaStride = std.Width;
            BitmapData jpgData = null, bmpData = null;

            using (var ms = new MemoryStream(buffer))
                using (var jpeg = (Bitmap)Image.FromStream(ms))
                    using (var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb)) {
                        try {
                            Rectangle rect = new Rectangle(0, 0, std.Width, std.Height);
                            jpgData = jpeg.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                            bmpData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
                            byte *pJpg = (byte *)jpgData.Scan0.ToPointer();
                            byte *pBmp = (byte *)bmpData.Scan0.ToPointer();

                            // Copy over the jpeg pixels first
                            if (expand)
                            {
                                for (int y = 0; y < std.Height; y++)
                                {
                                    int src = y * jpgStride;
                                    int dst = (y + offsetY) * stride + offsetX * depthBytes;
                                    Buffer.MemoryCopy(pJpg + src, pBmp + dst, bufferSize, jpgStride);
                                }
                            }
                            else
                            {
                                Buffer.MemoryCopy(pJpg, pBmp, bufferSize, bufferSize);
                            }

                            // Now apply the alpha to the pixels
                            for (int y = 0; y < std.Height; y++)
                            {
                                int src = y * alphaStride;
                                int dst = (y + offsetY) * stride;
                                for (int x = 0; x < std.Width; x++)
                                {
                                    int alphaIndex = src + x;
                                    int pixelIndex = dst + (x + offsetX) * depthBytes;

                                    pBmp[pixelIndex + 3] = alpha[alphaIndex];
                                }
                            }
                        } finally {
                            if (jpgData != null)
                            {
                                jpeg.UnlockBits(jpgData);
                            }
                            if (bmpData != null)
                            {
                                bitmap.UnlockBits(bmpData);
                            }
                        }
                        if (options.HasFlag(HgxOptions.Flip))
                        {
                            bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
                        }
                        bitmap.Save(pngFile, ImageFormat.Png);
                    }
        }
 /// <summary>
 ///  Extracts the HG-3 image information and images contained within HG-3 file to the output
 ///  <paramref name="outputDir"/>.
 /// </summary>
 /// <param name="stream">The open stream to the HG-3.</param>
 /// <param name="outputDir">The output directory to save the images to.</param>
 /// <param name="options">The options for manipulating the image during extraction.</param>
 /// <returns>The extracted <see cref="HgxImage"/> information.</returns>
 ///
 /// <exception cref="ArgumentNullException">
 ///  <paramref name="stream"/>, <paramref name="fileName"/>, or <paramref name="outputDir"/> is null.
 /// </exception>
 /// <exception cref="ObjectDisposedException">
 ///  The <paramref name="stream"/> is closed.
 /// </exception>
 public static HgxImage ExtractImages(Stream stream, string fileName, string outputDir, HgxOptions options)
 {
     return(ExtractInternal(stream, fileName, outputDir, options));
 }
        internal static void ExtractImage(Stream stream, HgxFrame frame, string pngFile, HgxOptions options)
        {
            switch (frame.HgxType)
            {
            case HgxFormat.Hg2: ExtractHg2ImageFromFrame(stream, frame, pngFile, options); break;

            case HgxFormat.Hg3: ExtractHg3ImageFromFrame(stream, frame, pngFile, options); break;

            default: throw new InvalidOperationException($"{nameof(HgxFrame.HgxType)} is not valid!");
            }
        }
        private static HgxImage ExtractInternal(Stream stream, string fileName, string outputDir, HgxOptions options)
        {
            BinaryReader reader = new BinaryReader(stream);
            HGXHDR       hdr    = reader.ReadUnmanaged <HGXHDR>();

            try {
                if (hdr.Signature == HGXHDR.ExpectedHg3Signature)
                {
                    return(ExtractHg3Internal(hdr, reader, fileName, outputDir, options));
                }
                if (hdr.Signature == HGXHDR.ExpectedHg2Signature)
                {
                    return(ExtractHg2Internal(hdr, reader, fileName, outputDir, options));
                }
            } catch (ArgumentNullException) {
                // This exception shouldn't be triggered by failure to read the file, so
                // we'll throw it as an error as itself because it's not supposed to happen.
                throw;
            } catch (NullReferenceException) {
                // This exception shouldn't be triggered by failure to read the file, so
                // we'll throw it as an error as itself because it's not supposed to happen.
                throw;
            } catch (Exception ex) {
                throw new HgxException(ex);
            }             /*catch (EndOfStreamException ex) {
                           *    throw new HgxException(ex);
                           * }*/
            throw new UnexpectedFileTypeException($"{HGXHDR.ExpectedHg2Signature} or {HGXHDR.ExpectedHg3Signature}");
        }