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}"); }