/// <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); } } }
private static byte[] ProcessImageInternal(BinaryReader reader, HG3STDINFO std, HG3IMG img, out byte[] rgbaBuffer) { int depthBytes = (std.DepthBits + 7) / 8; byte[] bufferTmp = reader.ReadBytes(img.DataLength); byte[] cmdBufferTmp = reader.ReadBytes(img.CmdLength); rgbaBuffer = new byte[std.Height * std.Width * 4]; ProcessImage( bufferTmp, img.DataLength, img.OriginalDataLength, cmdBufferTmp, img.CmdLength, img.OriginalCmdLength, out IntPtr pRgbaBuffer, out int rgbaLength, std.Width, std.Height, depthBytes); rgbaBuffer = new byte[rgbaLength]; Marshal.Copy(pRgbaBuffer, rgbaBuffer, 0, rgbaLength); Marshal.FreeHGlobal(pRgbaBuffer); return(rgbaBuffer); }
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 byte[] CopyFlipBufferAndDispose(IntPtr pRgbaBuffer, int stride, int height) { * try { * // Vertically flip the buffer so its in the correct setup to load into Bitmap * byte[] pixelBuffer = new byte[stride * height]; * for (int y = 0; y < height; y++) { * int src = y * stride; * int dst = (height - (y + 1)) * stride; * Marshal.Copy(pRgbaBuffer + src, pixelBuffer, dst, stride); * } * return pixelBuffer; * } finally { * Marshal.FreeHGlobal(pRgbaBuffer); * } * }*/ /*private static byte[] PrepareBufferAndDispose(IntPtr pRgbaBuffer, int stride, int height, bool flip) { * try { * byte[] pixelBuffer = new byte[stride * height]; * if (!flip) { * Marshal.Copy(pRgbaBuffer, pixelBuffer, 0, stride * height); * } * else { * // Vertically flip the buffer so its in the correct setup to load into Bitmap * for (int y = 0; y < height; y++) { * int src = y * stride; * int dst = (height - (y + 1)) * stride; * Marshal.Copy(pRgbaBuffer + src, pixelBuffer, dst, stride); * } * } * return pixelBuffer; * } finally { * Marshal.FreeHGlobal(pRgbaBuffer); * } * }*/ /*private static void FlipBuffer(byte[] rgbaBuffer, int width, int height, int depthBits, bool flip) { * if (!flip) * return; * int depthBytes = (depthBits + 7) / 8; * int stride = (width * depthBytes + 3) & ~3; * * // Vertically flip the buffer so its in the correct setup to load into Bitmap * byte[] strideBuffer = new byte[stride]; * // No issue if height is odd and we skip the middle line, we'd be swapping it with itself. * for (int y = 0; y < height / 2; y++) { * int src = y * stride; * int dst = (height - (y + 1)) * stride; * // Copy src to swap stride * Buffer.BlockCopy(rgbaBuffer, src, strideBuffer, 0, stride); * // Swap src with dst stride * Buffer.BlockCopy(rgbaBuffer, dst, rgbaBuffer, src, stride); * // Swap dst with swap stride * Buffer.BlockCopy(strideBuffer, 0, rgbaBuffer, dst, stride); * } * }*/ #endregion #region ExtractBitmap /// <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, HG3STDINFO 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 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); } }
/// <summary> /// Extracts the bitmap 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 ExtractBitmap(BinaryReader reader, HG3STDINFO std, HG3IMG img, bool expand, string pngFile) { int depthBytes = (std.DepthBits + 7) / 8; int stride = (std.Width * depthBytes + 3) / 4 * 4; byte[] bufferTmp = reader.ReadBytes(img.DataLength); byte[] cmdBufferTmp = reader.ReadBytes(img.CmdLength); byte[] rgbaBuffer; // Perform heavy processing that's faster in native code ProcessImageNative( bufferTmp, img.DataLength, img.OriginalDataLength, cmdBufferTmp, img.CmdLength, img.OriginalCmdLength, out IntPtr pRgbaBuffer, out int rgbaLength, std.Width, std.Height, depthBytes); try { // Vertically flip the buffer so its in the correct setup to load into Bitmap rgbaBuffer = new byte[rgbaLength]; for (int y = 0; y < std.Height; y++) { int src = y * stride; int dst = (std.Height - (y + 1)) * stride; Marshal.Copy(pRgbaBuffer + src, rgbaBuffer, dst, stride); } } finally { Marshal.FreeHGlobal(pRgbaBuffer); } WriteBitmap(rgbaBuffer, std, expand, pngFile); }
// This encoding tries to optimize for lots of zeros. I think. :) /*private static byte UnpackVal(byte c) { * byte z = (byte) ((c & 1) != 0 ? 0xFF : 0); * return (byte) ((c >> 1) ^ z); * } * * private static unsafe void UndeltaFilter(byte[] buffer, * //int length, * byte[] outBuffer, * int width, * int height, * int depthBytes) * { * uint[] table1 = new uint[256]; * uint[] table2 = new uint[256]; * uint[] table3 = new uint[256]; * uint[] table4 = new uint[256]; * * for (uint i = 0; i < 256; i++) { * uint val = i & 0xC0; * * val <<= 6; * val |= i & 0x30; * * val <<= 6; * val |= i & 0x0C; * * val <<= 6; * val |= i & 0x03; * * table4[i] = val; * table3[i] = val << 2; * table2[i] = val << 4; * table1[i] = val << 6; * } * * uint sectLength = (uint) buffer.Length / 4; * * fixed (byte* pOutBuffer = outBuffer) * fixed (byte* pBuffer = buffer) { * byte* sect1 = pBuffer; * byte* sect2 = sect1 + sectLength; * byte* sect3 = sect2 + sectLength; * byte* sect4 = sect3 + sectLength; * * byte* outP = pOutBuffer; * byte* outEnd = pOutBuffer + buffer.Length; * * while (outP < outEnd) { * uint val = table1[*sect1++] | table2[*sect2++] | table3[*sect3++] | table4[*sect4++]; * * outP++ = UnpackVal((byte) (val >> 0)); * outP++ = UnpackVal((byte) (val >> 8)); * outP++ = UnpackVal((byte) (val >> 16)); * outP++ = UnpackVal((byte) (val >> 24)); * } * * int stride = width * depthBytes; * * for (int x = depthBytes; x < stride; x++) { * outBuffer[x] += outBuffer[x - depthBytes]; * } * * for (uint y = 1; y < height; y++) { * byte* line = pOutBuffer + y * stride; * byte* prev = pOutBuffer + (y - 1) * stride; * * for (uint x = 0; x < stride; x++) { * line[x] += prev[x]; * } * } * } * } * * private static void Unrle(byte[] buffer, * byte[] cmdBuffer, * out byte[] outBuffer) * { * BitBuffer cmdBits = new BitBuffer(cmdBuffer); * * bool copyFlag = cmdBits.GetBit(); * * int outLength = (int) cmdBits.GetEliasGammaValue(); * outBuffer = new byte[outLength]; * * int n = 0; * int index = 0; * for (int i = 0; i < outLength; i += n) { * n = (int) cmdBits.GetEliasGammaValue(); * * if (copyFlag) { * Array.Copy(buffer, index, outBuffer, i, n); * index += n; * } * else { * Array.Clear(outBuffer, i, n); * } * * copyFlag = !copyFlag; * } * }*/ #endregion private static void ProcessImage(BinaryReader reader, string file, HG3STDINFO std, HG3IMG img, bool expand = true) { int depthBytes = (std.DepthBits + 7) / 8; int stride = (std.Width * depthBytes + 3) / 4 * 4; #region Old Code /*byte[] buffer = new byte[img.OriginalDataLength]; * { * byte[] temp = reader.ReadBytes(img.DataLength); * Uncompress(buffer, ref img.OriginalDataLength, * temp, img.DataLength); * } * * byte[] cmdBuffer = new byte[img.OriginalCmdLength]; * { * byte[] temp = reader.ReadBytes(img.CmdLength); * Uncompress(cmdBuffer, ref img.OriginalCmdLength, * temp, img.CmdLength); * } * * Unrle(buffer, cmdBuffer, out byte[] outBuffer);//, ref outLength); * * byte[] rgbaBuffer = new byte[outBuffer.Length]; * UndeltaFilter(outBuffer, rgbaBuffer, std.Width, std.Height, depthBytes); * UnrleDeltaFilter( * buffer, buffer.Length, * cmdBuffer, cmdBuffer.Length, * out IntPtr pRgbaBuffer, out int rgbaLength, * std.Width, * std.Height, * depthBytes);*/ /*byte[] bufferTmp = reader.ReadBytes(img.DataLength); * byte[] cmdBufferTmp = reader.ReadBytes(img.CmdLength); * * ProcessImage( * bufferTmp, * img.DataLength, * img.OriginalDataLength, * cmdBufferTmp, * img.CmdLength, * img.OriginalCmdLength, * out IntPtr pRgbaBuffer, * out int rgbaLength, * std.Width, * std.Height, * depthBytes); * * byte[] rgbaBuffer = new byte[rgbaLength]; * Marshal.Copy(pRgbaBuffer, rgbaBuffer, 0, rgbaLength); * Marshal.FreeCoTaskMem(pRgbaBuffer);*/ #endregion ProcessImageInternal(reader, std, img, out byte[] rgbaBuffer); //int depthBytes = std.DepthBits / 8; /*byte[] bufferTmp = reader.ReadBytes(img.DataLength); * byte[] cmdBufferTmp = reader.ReadBytes(img.CmdLength); * * int rgbaLength; * byte[] rgbaBuffer = new byte[std.Height * std.Width * 4]; * ProcessImage( * bufferTmp, * img.DataLength, * img.OriginalDataLength, * cmdBufferTmp, * img.CmdLength, * img.OriginalCmdLength, * rgbaBuffer, * //out IntPtr pRgbaBuffer, * out rgbaLength, * std.Width, * std.Height, * depthBytes); * if (rgbaBuffer.Length != rgbaLength) { * //Console.WriteLine(); * Console.WriteLine($"\n{rgbaLength - rgbaBuffer.Length} "); * Console.Beep(500, 2000); * return; * }*/ if (expand) { int offsetXBytes = std.OffsetX * depthBytes; //int offsetYVert = std.TotalHeight - std.OffsetY - std.Height; int expStride = (std.TotalWidth * depthBytes + 3) / 4 * 4; byte[] expBuffer = new byte[std.TotalHeight * expStride]; for (int y = 0; y < std.Height; y++) { int src = y * stride; //int dst = (y + offsetYVert) * expStride + offsetXBytes; int dst = (std.Height - (y + 1) + std.OffsetY) * expStride + offsetXBytes; Array.Copy(rgbaBuffer, src, expBuffer, dst, stride); } std.Width = std.TotalWidth; std.Height = std.TotalHeight; rgbaBuffer = expBuffer; } else { byte[] flipBuffer = new byte[rgbaBuffer.Length]; for (int y = 0; y < std.Height; y++) { int src = y * stride; int dst = (std.Height - (y + 1)) * stride; Array.Copy(rgbaBuffer, src, flipBuffer, dst, stride); } rgbaBuffer = flipBuffer; } WritePng(file, rgbaBuffer, std.Width, std.Height, std.DepthBits); }
public static string[] Run(Stream stream, string hg3File, string outputDir, string fileName, bool expand) { BinaryReader reader = new BinaryReader(stream); HG3HDR hdr = reader.ReadStruct <HG3HDR>(); if (hdr.Signature != "HG-3") { throw new InvalidFileException(Path.GetFileName(hg3File), "HG3"); } List <string> files = new List <string>(); int backtrack = Marshal.SizeOf <HG3TAG>() - 1; for (int i = 0; true; i++) { HG3TAG tag = reader.ReadStruct <HG3TAG>(); // NEW METHOD: Keep searching for the next stdinfo // This way we don't miss any images while (!tag.Signature.StartsWith("stdinfo")) { if (stream.IsEndOfStream()) { break; } stream.Position -= backtrack; tag = reader.ReadStruct <HG3TAG>(); } if (stream.IsEndOfStream()) { break; } // OLD METHOD: Missed entries in a few files //if (!tag.signature.StartsWith(StdInfoSignature)) // break; HG3STDINFO stdInfo = reader.ReadStruct <HG3STDINFO>(); int imgIndex = 0; while (tag.OffsetNext != 0) { tag = reader.ReadStruct <HG3TAG>(); string pngFile = Path.Combine(outputDir, MakeFileName( fileName, i > 0 || hdr.EntryCount > 0, i, imgIndex++)); /*if (tag.Signature.StartsWith("img_al")) { * // Skip this tag * stream.Position += tag.Length; * HG3IMGAL imghdr = reader.ReadStruct<HG3IMGAL>(); * * int length = imghdr.length; * byte[] buffer = reader.ReadBytes(length); * * int outLength = imghdr.original_length; * byte[] outBuffer = reader.ReadBytes(outLength); * * Uncompress(outBuffer, ref outLength, buffer, length); * * files.Add(pngFile); * WritePng(pngFile, * outBuffer, * stdInfo.width, * stdInfo.height, * 1, * true); * } * else if (tag.Signature.StartsWith("img_jpg")) { * // Skip this tag * stream.Position += tag.Length; * } * else if (tag.Signature == "imgmode") { * // Skip this tag * stream.Position += tag.Length; * } * else */if (Regex.IsMatch(tag.Signature, @"img\d+")) { HG3IMG imghdr = reader.ReadStruct <HG3IMG>(); files.Add(pngFile); ProcessImage(reader, pngFile, stdInfo, imghdr, expand); /*stdInfo.Width, * stdInfo.Height, * stdInfo.DepthBits / 8, * stdInfo.TotalWidth, * stdInfo.TotalHeight, * stdInfo.OffsetX, * stdInfo.OffsetY, * imghdr.DataLength, * imghdr.OriginalDataLength, * imghdr.CmdLength, * imghdr.OriginalCmdLength);*/ } else { // Skip this tag stream.Position += tag.Length; } } stream.Position += 8; } return(files.ToArray()); }
private static Hg3FrameInfo ReadHg3FrameInfo(BinaryReader reader, HGXHDR hdr, bool frameOnly) { Stream stream = reader.BaseStream; long frameOffset = stream.Position; HG3FRAMEHDR frameHdr = reader.ReadUnmanaged <HG3FRAMEHDR>(); long tagStartPosition = stream.Position; HG3TAG tag = reader.ReadUnmanaged <HG3TAG>(); if (!HG3STDINFO.HasTagSignature(tag.Signature)) { throw new Exception("Expected \"stdinfo\" tag!"); } HG3STDINFO stdInfo = reader.ReadUnmanaged <HG3STDINFO>(); Hg3FrameInfo frameInfo = new Hg3FrameInfo(frameHdr, stdInfo, frameOffset); while (tag.OffsetNext != 0) { stream.Position = tagStartPosition + tag.OffsetNext; tagStartPosition = stream.Position; tag = reader.ReadUnmanaged <HG3TAG>(); string signature = tag.Signature; if (HG3IMG.HasTagSignature(signature, out int imdId)) // "img####" { HG3IMG img = reader.ReadUnmanaged <HG3IMG>(); frameInfo.AddTagImg(stream, tag, img, imdId); } else if (HG3IMG_AL.HasTagSignature(signature)) // "img_al" { HG3IMG_AL img = reader.ReadUnmanaged <HG3IMG_AL>(); frameInfo.AddTagImg(stream, tag, img, 0); } else if (HG3IMG_JPG.HasTagSignature(signature)) // "img_jpg" // There is no image info, reading it would increment by one byte which we don't want { frameInfo.AddTagImg(stream, tag, new HG3IMG_JPG(), 0); } else if (frameOnly) { if (frameInfo.Type != Hg3ImageType.None) { return(frameInfo); } } else if (HG3ATS.HasTagSignature(signature, out int atsId)) // "ats####" { HG3ATS ats = reader.ReadUnmanaged <HG3ATS>(); frameInfo.Ats.Add(atsId, ats); } else if (HG3CPTYPE.HasTagSignature(signature)) // "cptype" { HG3CPTYPE cpType = reader.ReadUnmanaged <HG3CPTYPE>(); frameInfo.CpType = cpType; } else if (HG3IMGMODE.HasTagSignature(signature)) // "imgmode" { HG3IMGMODE imgMode = reader.ReadUnmanaged <HG3IMGMODE>(); frameInfo.ImgMode = imgMode; } else { Trace.WriteLine($"UNKNOWN TAG: \"{signature}\""); } } return(frameInfo); }
private static Hg3 Extract(Stream stream, string fileName, string directory, bool saveFrames, bool expand) { BinaryReader reader = new BinaryReader(stream); HG3HDR hdr = reader.ReadUnmanaged <HG3HDR>(); if (hdr.Signature != "HG-3") { throw new UnexpectedFileTypeException(fileName, "HG-3"); } //int backtrack = Marshal.SizeOf<HG3TAG>() - 1; List <KeyValuePair <HG3STDINFO, List <long> > > imageOffsets = new List <KeyValuePair <HG3STDINFO, List <long> > >(); for (int i = 0; ; i++) { // 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. long startPosition = stream.Position; HG3OFFSET offset = reader.ReadUnmanaged <HG3OFFSET>(); HG3TAG tag = reader.ReadUnmanaged <HG3TAG>(); if (!HG3STDINFO.HasTagSignature(tag.Signature)) { throw new Exception("Expected \"stdinfo\" tag!"); } // NEW METHOD: Keep searching for the next stdinfo // This way we don't miss any images /*int offset = 0; * while (!tag.Signature.StartsWith("stdinfo")) { * if (stream.IsEndOfStream()) * break; * stream.Position -= backtrack; * tag = reader.ReadStruct<HG3TAG>(); * offset++; * } * if (stream.IsEndOfStream()) * break;*/ // OLD METHOD: Missed entries in a few files //if (!tag.signature.StartsWith(StdInfoSignature)) // break; HG3STDINFO stdInfo = reader.ReadUnmanaged <HG3STDINFO>(); List <long> frameOffsets = new List <long>(); imageOffsets.Add(new KeyValuePair <HG3STDINFO, List <long> >(stdInfo, frameOffsets)); while (tag.OffsetNext != 0) { tag = reader.ReadUnmanaged <HG3TAG>(); string signature = tag.Signature; if (HG3IMG.HasTagSignature(signature)) // "img####" { frameOffsets.Add(stream.Position); // Skip this tag stream.Position += tag.Length; } /*else if (HG3ATS.HasTagSignature(signature)) { // "ats####" * // Skip this tag * stream.Position += tag.Length; * } * else if (HG3CPTYPE.HasTagSignature(signature)) { // "cptype" * // Skip this tag * stream.Position += tag.Length; * } * else if (HG3IMG_AL.HasTagSignature(signature)) { // "img_al" * // Skip this tag * stream.Position += tag.Length; * } * else if (HG3IMG_JPG.HasTagSignature(signature)) { // "img_jpg" * // Skip this tag * stream.Position += tag.Length; * } * else if (HG3IMGMODE.HasTagSignature(signature)) { // "imgmode" * // Skip this tag * stream.Position += tag.Length; * }*/ else { // Skip this unknown tag stream.Position += tag.Length; } } if (offset.OffsetNext == 0) { break; // End of stream } stream.Position = startPosition + offset.OffsetNext; } HG3STDINFO[] stdInfos = imageOffsets.Select(p => p.Key).ToArray(); long[][] allFrameOffsets = imageOffsets.Select(p => p.Value.ToArray()).ToArray(); Hg3 hg3 = new Hg3(Path.GetFileName(fileName), hdr, stdInfos, allFrameOffsets, saveFrames && expand); // Save any frames after we've located them all. // This way we truely know if something is an animation. if (saveFrames) { for (int imgIndex = 0; imgIndex < hg3.Count; imgIndex++) { HG3STDINFO stdInfo = stdInfos[imgIndex]; Hg3Image hg3Image = hg3.Images[imgIndex]; for (int frmIndex = 0; frmIndex < hg3Image.FrameCount; frmIndex++) { stream.Position = hg3Image.FrameOffsets[frmIndex]; HG3IMG imghdr = reader.ReadUnmanaged <HG3IMG>(); string pngFile = hg3.GetFrameFilePath(directory, imgIndex, frmIndex); ExtractBitmap(reader, stdInfo, imghdr, expand, pngFile); } } } return(hg3); }
/// <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); } }