diff --git a/ComicCompress.csproj b/ComicCompress.csproj index de3c08b..27145e2 100644 --- a/ComicCompress.csproj +++ b/ComicCompress.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 ComicCompressor ComicCompress True diff --git a/Compressor.cs b/Compressor.cs index 886d80e..f11f16d 100644 --- a/Compressor.cs +++ b/Compressor.cs @@ -14,6 +14,9 @@ using javax.swing.plaf; using SharpCompress.Readers; using SharpCompress.Writers; using SharpCompress.Compressors.Xz; +//using SixLabors.ImageSharp; +//using SixLabors.ImageSharp; +//using SixLabors.ImageSharp.Processing; namespace ComicCompressor { @@ -29,7 +32,7 @@ namespace ComicCompressor Logger = logger; } - public void Compress(string filename, string outputFile = null) + public void Compress(string filename, string outputFile = null) { Stopwatch sw = Stopwatch.StartNew(); outputFile = Path.ChangeExtension(outputFile ?? filename, "cbz"); @@ -72,18 +75,18 @@ namespace ComicCompressor } //Logger.Log($"Processing[{archive.Entries.Count()}]: " + filename, LogLevel.Verbose); - Logger.Log($"Processing[{archive.Entries.Count()}]: ", LogLevel.Verbose,false); - Logger.Log( filename, LogLevel.Verbose,col:Color.Green); + Logger.Log($"Processing[{archive.Entries.Count()}]: ", LogLevel.Verbose, false); + Logger.Log(filename, LogLevel.Verbose, col: Color.Green); double save = 0; - int num_pages =0; + int num_pages = 0; if (isSolid) { using (Stream stream = File.OpenRead(filename)) { using (var reader = ReaderFactory.Open(stream)) { - (save, num_pages) = ProcessReader(archive,reader, outputFile); + (save, num_pages) = ProcessReader(archive, reader, outputFile); } } } @@ -91,13 +94,13 @@ namespace ComicCompressor (save, num_pages) = ProcessArchive(archive, outputFile, filename); if (num_pages == 0) num_pages = 1; long elapsed = sw.ElapsedMilliseconds; - Logger.Log($"\r\nFinished({save.ToString("0.##")}%) [{elapsed}][{elapsed/num_pages}]: " + filename, LogLevel.Verbose, col: Color.Green); + Logger.Log($"\r\nFinished({save.ToString("0.##")}%) [{elapsed}][{elapsed / num_pages}]: " + filename, LogLevel.Verbose, col: Color.Green); } private (double, int) ProcessReader(IArchive archive, IReader reader, string outputPath) { var encoder = new SimpleEncoder(); - + bool cover = true; double save = 0.0; @@ -125,6 +128,7 @@ namespace ComicCompressor !entry_key_lower.EndsWith(".png") && !entry_key_lower.EndsWith(".bmp") && !entry_key_lower.EndsWith(".ppm") && + !entry_key_lower.EndsWith(".pbm") && !entry_key_lower.EndsWith(".gif") && !entry_key_lower.EndsWith(".jpeg")) { @@ -169,7 +173,7 @@ namespace ComicCompressor save = 100 * (double)output_length / (double)archive.TotalSize; - return (save,nop); + return (save, nop); } public int GetPlace(int value, int place) { @@ -189,10 +193,10 @@ namespace ComicCompressor { var imageStreams = new List(); int count = 0; - foreach (var entry in fileEntries) + foreach (var entry in fileEntries) { (bool res, bool skipped) = ProcessEntry(entry, imageStreams, output, encoder, cover); - if (res) + if (res) cover = false; count++; if (!skipped) @@ -202,15 +206,15 @@ namespace ComicCompressor string digit = GetPlace(count, 10).ToString(); Logger.Log(digit, LogLevel.Verbose, false, col: Color.Green); } - + } archive.Dispose(); - + Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); output.SaveTo(outputPath, CompressionType.Deflate); - + //output.SaveTo(outputPath, CompressionType.None); imageStreams.ForEach(i => i.Dispose()); @@ -223,15 +227,15 @@ namespace ComicCompressor save = 100 * (double)output_length / (double)archive.TotalSize; Program.file_stat fs = null; - Program.File_statistics.TryGetValue(inputPath,out fs); - if(fs != null) + Program.File_statistics.TryGetValue(inputPath, out fs); + if (fs != null) { fs.Uncompressed_file_len = archive.TotalSize; fs.Compressed_file_len = output_length; Program.File_statistics.TryAdd(inputPath, fs); } - + return (save, nop); } @@ -243,25 +247,27 @@ namespace ComicCompressor { if (entry.IsEncrypted) { - Logger.Log("X", LogLevel.Verbose, false, col: Color.Yellow); - return (false,true); + Logger.Log("X", LogLevel.Verbose, false, col: Color.Yellow); + return (false, true); } using (var stream = entry.OpenEntryStream()) { - + if (entry.Key.StartsWith("__MACOSX")) { - return (false,true); + return (false, true); } string entry_key_lower = entry.Key.ToLower(); bool isPPM = entry_key_lower.EndsWith(".ppm"); + bool isPBM = entry_key_lower.EndsWith(".pbm"); - if (!entry_key_lower.EndsWith(".jpg") && + if (!entry_key_lower.EndsWith(".jpg") && !entry_key_lower.EndsWith(".png") && !entry_key_lower.EndsWith(".bmp") && !entry_key_lower.EndsWith(".ppm") && + !entry_key_lower.EndsWith(".pbm") && !entry_key_lower.EndsWith(".gif") && !entry_key_lower.EndsWith(".jpeg")) { @@ -273,13 +279,13 @@ namespace ComicCompressor return (false, true); } - if (cover && !isPPM) + if (cover && !isPPM && !isPBM) { var ms = new MemoryStream(); imageStreams.Add(ms); stream.CopyTo(ms); output.AddEntry(entry.Key, ms); - Logger.Log("C", LogLevel.Verbose, false,col:Color.Green); + Logger.Log("C", LogLevel.Verbose, false, col: Color.Green); return (true, false); } @@ -288,8 +294,8 @@ namespace ComicCompressor var raw_ms = new MemoryStream(); stream.CopyTo(raw_ms); try - { - if(isPPM) + { + if (isPPM) { Logger.Log("P", LogLevel.Verbose, false, col: Color.PaleGreen); PixelMap ppm = new PixelMap(raw_ms); @@ -297,6 +303,14 @@ namespace ComicCompressor bits = ppm.BitMap; Logger.Log("\b", LogLevel.Verbose, false, col: Color.PaleGreen); } + else if (isPBM) + { + Logger.Log("B", LogLevel.Verbose, false, col: Color.PaleGreen); + PixelMap pbm = new PixelMap(raw_ms); + bits = pbm.BitMap; + Logger.Log("\b", LogLevel.Verbose, false, col: Color.PaleGreen); + + } else { bits = new Bitmap(raw_ms); @@ -305,7 +319,7 @@ namespace ComicCompressor catch (Exception e) { Logger.LogError("Error parsing bitmap: " + entry.Key, col: Color.Red); - Logger.LogDebug(e, LogLevel.Error, col:Color.Red); + Logger.LogDebug(e, LogLevel.Error, col: Color.Red); return (false, true); } @@ -320,7 +334,7 @@ namespace ComicCompressor { var ms = new MemoryStream(); encoder.Encode(bits, ms, Quality); - if (ms.Length < entry.Size) + if (ms.Length < entry.Size || isPBM) { imageStreams.Add(ms); output.AddEntry(entry.Key + ".webp", ms); @@ -364,15 +378,15 @@ namespace ComicCompressor Process proc = Process.GetCurrentProcess(); //Logger.LogDebug($"M {proc.PrivateMemorySize64} - D {proc.PrivateMemorySize64 - last_mem}", LogLevel.Verbose); //last_mem = proc.PrivateMemorySize64; - return (false,false); + return (false, false); } - private void ProcessReaderEntry(IReader reader,EntryStream entry, IList imageStreams, ZipArchive output, SimpleEncoder encoder) + private void ProcessReaderEntry(IReader reader, EntryStream entry, IList imageStreams, ZipArchive output, SimpleEncoder encoder) { try { var stream = entry; -// using (var stream = entry.OpenEntryStream()) + // using (var stream = entry.OpenEntryStream()) { @@ -389,7 +403,7 @@ namespace ComicCompressor { Logger.LogError("Error parsing bitmap: " + reader.Entry.Key, col: Color.Red); Logger.LogDebug(e, LogLevel.Error, col: Color.Red); - return ; + return; } if (bits.PixelFormat != System.Drawing.Imaging.PixelFormat.Format24bppRgb) @@ -435,14 +449,14 @@ namespace ComicCompressor return; } - return ; + return; } private Bitmap ChangePixelFormat(Bitmap orig, System.Drawing.Imaging.PixelFormat format = System.Drawing.Imaging.PixelFormat.Format24bppRgb) { Bitmap clone = new Bitmap(orig.Width, orig.Height, format); - using (Graphics gr = Graphics.FromImage(clone)) + using (Graphics gr = Graphics.FromImage(clone)) { gr.DrawImage(orig, new Rectangle(0, 0, clone.Width, clone.Height)); } diff --git a/PixelMap.cs b/PixelMap.cs index c475c4d..f8d2432 100644 --- a/PixelMap.cs +++ b/PixelMap.cs @@ -1,10 +1,15 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Text; +using System.Threading.Tasks; + +using System.Threading; + //using Microsoft.Xna.Framework; //using Microsoft.Xna.Framework.Graphics; @@ -202,7 +207,10 @@ namespace ComicCompressor this.bytesPerPixel = 3; break; case "P4": - throw new Exception("Binary .pbm (Magic Number P4) is not supported at this time."); + this.pixelFormat = PixelFormat.Format8bppIndexed; + this.bytesPerPixel = 1; + //throw new Exception("Binary .pbm (Magic Number P4) is not supported at this time."); + break; case "P5": // 1 byte per pixel this.pixelFormat = PixelFormat.Format8bppIndexed; this.bytesPerPixel = 1; @@ -243,6 +251,11 @@ namespace ComicCompressor } } } + else if(this.header.MagicNumber == "P4") + { + byte[] pixels = ReadP4(this.Header.Width, this.Header.Height, binReader); + this.imageData = pixels; + } else // binary encoding. { int bytesLeft = (int)(binReader.BaseStream.Length - binReader.BaseStream.Position); @@ -283,6 +296,88 @@ namespace ComicCompressor } } + private static byte[] ReadP4(int Width, int Height, BinaryReader binReader) + { + + (int imageWidth, int imageHeight) = (Width, Height); + int srcStride = (imageWidth + (8 - 1)) / 8; // Ceiling + int srcSize = imageHeight * srcStride; + var pixels = new byte[Height * Width * 1+8]; + //byte[] buffer = ArrayPool.Shared.Rent(srcSize); + try + { + int charsLeft = (int)(binReader.BaseStream.Length - binReader.BaseStream.Position); + byte[] buffer = binReader.ReadBytes(charsLeft); // read all the data into an array in one go, for efficiency. + string valueString = string.Empty; + //index = 0; + + //int readSize = await stream.ReadAsync(buffer.AsMemory(0, srcSize), cancellationToken); + //if (readSize != srcSize) + // throw new NotImplementedException($"Unable to read the intended size. Expected={srcSize}, Actual={readSize}"); + + unsafe + { + fixed (byte* destHeadPtr = pixels) + fixed (byte* srcHeadPtr = buffer) + { + byte* srcTailPtr = srcHeadPtr + (srcStride * imageHeight); + byte* dest = destHeadPtr; + (int srcWidthMax, int remainder) = Math.DivRem(imageWidth, 8); + + for (byte* srcRowPtr = srcHeadPtr; srcRowPtr < srcTailPtr; srcRowPtr += srcStride) + { + byte* srcRowTailPtr = srcRowPtr + srcWidthMax; + for (byte* p = srcRowPtr; p < srcRowTailPtr; p++) + { + //Console.Write($" {(*p).ToString("X2")}"); + + //*((ulong*)dest) = + // (((*p & 0x80) is 0) ? 0UL : 0x0000_0000_0000_0001) + // | (((*p & 0x40) is 0) ? 0UL : 0x0000_0000_0000_0100) + // | (((*p & 0x20) is 0) ? 0UL : 0x0000_0000_0001_0000) + // | (((*p & 0x10) is 0) ? 0UL : 0x0000_0000_0100_0000) + // | (((*p & 0x08) is 0) ? 0UL : 0x0000_0001_0000_0000) + // | (((*p & 0x04) is 0) ? 0UL : 0x0000_0100_0000_0000) + // | (((*p & 0x02) is 0) ? 0UL : 0x0001_0000_0000_0000) + // | (((*p & 0x01) is 0) ? 0UL : 0x0100_0000_0000_0000); + *((ulong*)dest) = + (((*p & 0x80) is 0) ? 0UL : 0x0000_0000_0000_00ff) + | (((*p & 0x40) is 0) ? 0UL : 0x0000_0000_0000_ff00) + | (((*p & 0x20) is 0) ? 0UL : 0x0000_0000_00ff_0000) + | (((*p & 0x10) is 0) ? 0UL : 0x0000_0000_ff00_0000) + | (((*p & 0x08) is 0) ? 0UL : 0x0000_00ff_0000_0000) + | (((*p & 0x04) is 0) ? 0UL : 0x0000_ff00_0000_0000) + | (((*p & 0x02) is 0) ? 0UL : 0x00ff_0000_0000_0000) + | (((*p & 0x01) is 0) ? 0UL : 0xff00_0000_0000_0000); + + dest += sizeof(ulong); + + } + + if (remainder > 0) + { + const byte start = 0x80; + byte end = (byte)(start >> remainder); + + for (int mask = start; mask > end; mask >>= 1) + *(dest++) = ((*srcRowTailPtr & mask) is 0) ? (byte)0 : (byte)1; + } + } + } + } + return pixels; + } + catch(Exception ex) + { + Console.WriteLine(ex.Message + " - " + ex.StackTrace); + } + finally + { + } + return pixels; + } + + /// /// As it stands, the native byte order in .pbm, .pgm, and .ppm images is BGR. We need to reverse the order /// into RGB as this is what is needed for 24bppRGB bitmap pixel format on Windows.