Compressor based on compressor-core

This commit is contained in:
Marco Giuntoni
2025-03-27 19:01:22 +01:00
parent 87b7ef4fdf
commit 89812c7fa6
13 changed files with 75 additions and 1070 deletions
+16 -4
View File
@@ -8,20 +8,28 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Remove="FileParser.cs" />
<Compile Remove="ImageExtractor.cs" />
<Compile Remove="ImageExtractor7.cs" />
<Compile Remove="Logger.cs" />
<Compile Remove="PixelMap.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryKits.Zpl.Viewer" Version="1.2.1" />
<PackageReference Include="GroupDocs.Parser" Version="23.8.0" />
<PackageReference Include="Imazen.WebP" Version="10.0.1" />
<PackageReference Include="itext7" Version="8.0.1" />
<PackageReference Include="iTextSharp" Version="5.5.13.3" />
<PackageReference Include="iTextSharp" Version="5.5.13.4" />
<PackageReference Include="LibHeifSharp" Version="3.2.0" />
<PackageReference Include="Magick.NET-Q8-x64" Version="13.6.0" />
<PackageReference Include="Magick.NET.SystemDrawing" Version="7.2.2" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageReference Include="Pastel" Version="5.1.0" />
<PackageReference Include="Pastel" Version="6.0.0" />
<PackageReference Include="Pdfbox" Version="1.1.1" />
<PackageReference Include="sharpcompress" Version="0.37.2" />
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
<PackageReference Include="sharpcompress" Version="0.38.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.0" />
<PackageReference Include="Xabe.FFmpeg" Version="5.2.6" />
<PackageReference Include="Xabe.FFmpeg.Downloader" Version="5.2.6" />
</ItemGroup>
@@ -30,5 +38,9 @@
<Content CopyToOutputDirectory="PreserveNewest" Include="libwebp.dll" Link="libwebp.dll" Pack="true" PackagePath="libwebp.dll" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\iMGcompress-core\iMGcompress-core.csproj" />
</ItemGroup>
</Project>
+1
View File
@@ -5,6 +5,7 @@
</PropertyGroup>
<PropertyGroup>
<ActiveDebugProfile>DEV_HOME</ActiveDebugProfile>
<_LastSelectedProfileId>E:\_COMIX\_TOOLS_\comic-compress-new\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
+12
View File
@@ -5,6 +5,10 @@ VisualStudioVersion = 17.5.33530.505
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComicCompress", "ComicCompress.csproj", "{2625C71D-E4EC-4768-BF04-87AAB364916A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iMGcompress", "..\iMGcompress\iMGcompress.csproj", "{7330E00C-6C24-44B3-9768-1604802532FB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iMGcompress-core", "..\iMGcompress-core\iMGcompress-core.csproj", "{990E9DC1-B842-4E0E-AD7F-00CB2EF24AF4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +19,14 @@ Global
{2625C71D-E4EC-4768-BF04-87AAB364916A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2625C71D-E4EC-4768-BF04-87AAB364916A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2625C71D-E4EC-4768-BF04-87AAB364916A}.Release|Any CPU.Build.0 = Release|Any CPU
{7330E00C-6C24-44B3-9768-1604802532FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7330E00C-6C24-44B3-9768-1604802532FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7330E00C-6C24-44B3-9768-1604802532FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7330E00C-6C24-44B3-9768-1604802532FB}.Release|Any CPU.Build.0 = Release|Any CPU
{990E9DC1-B842-4E0E-AD7F-00CB2EF24AF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{990E9DC1-B842-4E0E-AD7F-00CB2EF24AF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{990E9DC1-B842-4E0E-AD7F-00CB2EF24AF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{990E9DC1-B842-4E0E-AD7F-00CB2EF24AF4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+3 -1
View File
@@ -14,6 +14,7 @@ using javax.swing.plaf;
using SharpCompress.Readers;
using SharpCompress.Writers;
using SharpCompress.Compressors.Xz;
using iMGcompress_core;
//using SixLabors.ImageSharp;
//using SixLabors.ImageSharp;
//using SixLabors.ImageSharp.Processing;
@@ -213,7 +214,8 @@ namespace ComicCompressor
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
output.SaveTo(outputPath, CompressionType.Deflate);
// output.SaveTo(outputPath, CompressionType.Deflate);
output.SaveTo(outputPath, CompressionType.None);
//output.SaveTo(outputPath, CompressionType.None);
-46
View File
@@ -1,46 +0,0 @@
using System.Collections.Generic;
using System.IO;
namespace ComicCompressor
{
public class FileParser
{
public IList<string> ListAllFiles(string input, bool recursive)
{
Queue<string> unexplored;
IList<string> files = new List<string>();
if (Directory.Exists(input))
{
unexplored = new Queue<string>(Directory.GetFileSystemEntries(input));
}
else
{
// No directory but must exist so it is a file
unexplored = new Queue<string>();
unexplored.Enqueue(input);
}
while(unexplored.Count > 0)
{
var file = unexplored.Dequeue();
if (File.Exists(file))
{
files.Add(file);
}
else if (recursive)
{
foreach (var f in Directory.GetFileSystemEntries(file))
{
unexplored.Enqueue(f);
}
}
}
return files;
}
}
}
-205
View File
@@ -1,205 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.ConstrainedExecution;
using ikvm.extensions;
using Imazen.WebP;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using Path = System.IO.Path;
namespace ComicCompressor
{
/// <summary>
/// Helper lass to dump all images from a PDF into separate files
/// </summary>
public class ImageExtractor : IRenderListener
{
int _currentPage = 1;
int _imageCount = 0;
readonly string _outputFilePrefix = "";
//readonly string _outputFolder;
//readonly bool _overwriteExistingFiles;
private bool Cover = true;
private Logger Logger { get; set; }
SimpleEncoder encoder = new SimpleEncoder();
public int Quality { get; set; } = 75;
private List<Stream> imageStreams = null;
public ZipArchive output = null;
public ImageExtractor(Logger logger)
{
Logger = logger;
//_outputFilePrefix = outputFilePrefix;
//_outputFolder = outputFolder;
//_overwriteExistingFiles = overwriteExistingFiles;
}
public int GetPlace(int value, int place)
{
return ((value % (place * 10)) - (value % place)) / place;
}
/// <summary>
/// Extract all images from a PDF file
/// </summary>
/// <param name="pdfPath">Full path and file name of PDF file</param>
/// <param name="outputFilePrefix">Basic name of exported files. If null then uses same name as PDF file.</param>
/// <param name="outputFolder">Where to save images. If null or empty then uses same folder as PDF file.</param>
/// <param name="overwriteExistingFiles">True to overwrite existing image files, false to skip past them</param>
/// <returns>Count of number of images extracted.</returns>
public int ExtractImagesFromFile(string pdfPath, string outputFolder, int quality = 75)
{
// Handle setting of any default values
//outputFilePrefix = outputFilePrefix ?? Path.GetFileNameWithoutExtension(pdfPath);
//outputFolder = String.IsNullOrEmpty(outputFolder) ? Path.GetDirectoryName(pdfPath) : outputFolder;
quality = quality;
Stopwatch sw = Stopwatch.StartNew();
using (var pdfReader = new PdfReader(pdfPath))
{
Logger.Log($"Processing[{pdfReader.NumberOfPages}]: " + pdfPath, LogLevel.Verbose);
if (pdfReader.IsEncrypted())
throw new ApplicationException(pdfPath + " is encrypted.");
var pdfParser = new PdfReaderContentParser(pdfReader);
output = ZipArchive.Create();
{
imageStreams = new List<Stream>();
int count = 0;
Cover = true;
while (_currentPage <= pdfReader.NumberOfPages)
{
pdfParser.ProcessContent(_currentPage, this);
_currentPage++;
count++;
if (count % 10 == 0)
{
string digit = GetPlace(count, 10).ToString();
Logger.Log(digit, LogLevel.Verbose, false);
}
}
Directory.CreateDirectory(Path.GetDirectoryName(outputFolder));
output.SaveTo(outputFolder, CompressionType.Deflate);
//output.SaveTo(outputPath, CompressionType.None);
imageStreams.ForEach(i => i.Dispose());
}
output.Dispose();
int nop = pdfReader.NumberOfPages;
if (nop == 0) nop = 1;
long elapsed = sw.ElapsedMilliseconds;
//Logger.Log($"\r\nFinished({save.ToString("0.##")}): " + pdfPath, LogLevel.Verbose);
Logger.Log($"\r\nFinished({0.ToString("0.##")} [{elapsed}][{elapsed / nop}]): " + pdfPath, LogLevel.Verbose);
}
return _imageCount;
}
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))
{
gr.DrawImage(orig, new Rectangle(0, 0, clone.Width, clone.Height));
}
return clone;
}
#region Implementation of IRenderListener
public void BeginTextBlock()
{
}
public void EndTextBlock()
{
}
public void RenderText(TextRenderInfo renderInfo)
{
var text = renderInfo.GetText();
}
public void RenderImage(ImageRenderInfo renderInfo)
{
try
{
var type = renderInfo.GetType();
var str = renderInfo.toString();
//var ftype = renderInfo.GetFileType();
var imageObject = renderInfo.GetImage();
//var imageFileName = String.Format("{0}_{1}_{2}.{3}", _outputFilePrefix, _currentPage, _imageCount,
// imageObject.GetFileType());
var imageFileName = String.Format($"{_imageCount.ToString("0000")}");
//var imagePath = Path.Combine(_outputFolder, imageFileName);
//if (_overwriteExistingFiles || !File.Exists(imagePath))
{
var imageRawBytes = imageObject.GetImageAsBytes();
var image = imageObject.GetDrawingImage();
//File.WriteAllBytes(imagePath, imageRawBytes);
if (image.Width < 512 || image.Height < 512)
return;
if (image.PixelFormat != System.Drawing.Imaging.PixelFormat.Format24bppRgb)
{
var newBits = ChangePixelFormat((Bitmap)image);
image.Dispose();
image = newBits;
}
var ms = new MemoryStream();
encoder.Encode((Bitmap)image, ms, Quality);
if (!Cover && ms.Length < imageRawBytes.Length)
{
imageStreams.Add(ms);
output.AddEntry(imageFileName + ".webp", ms);
Logger.Log(".", LogLevel.Verbose, false);
}
else
{
var mso = new MemoryStream();
mso.Write(imageRawBytes);
output.AddEntry(imageFileName + imageObject.GetFileType(), mso);
Logger.Log(Cover ? "C" : "=", LogLevel.Verbose, false);
Cover = false;
}
}
// Subtle: Always increment even if file is not written. This ensures consistency should only some
// of a PDF file's images actually exist.
_imageCount++;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
#endregion // Implementation of IRenderListener
}
}
-89
View File
@@ -1,89 +0,0 @@
using System;
using System.IO;
using System.Linq;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Xobject;
using iText.Layout;
using iText.Layout.Element;
using java.util;
using org.apache.pdfbox.pdmodel.graphics.xobject;
using org.apache.pdfbox.pdmodel;
namespace ComicCompressor
{
public class ImageExtractor7
{
public static readonly String DEST = "K:\\_COMIX\\_TOOLS_\\comic-compress-master\\bin\\Debug\\net6.0\\_NEW_PDF_\\Fan SUP 18.cbz";
public static readonly String SRC = "K:\\_COMIX\\_TOOLS_\\comic-compress-master\\bin\\Debug\\net6.0\\_PDF_\\Fan SUP 18.pdf";
public static void Main7()
{
FileInfo file = new FileInfo(DEST);
file.Directory.Create();
new ImageExtractor7().ManipulatePdf(DEST);
}
protected void ManipulatePdf(String dest)
{
PdfDocument resultDoc = new PdfDocument(new PdfWriter(dest));
PdfDocument srcDoc = new PdfDocument(new PdfReader(SRC));
// Assume that there is a single XObject in the source document
// and this single object is an image.
PdfDictionary pageDict = srcDoc.GetFirstPage().GetPdfObject();
PdfDictionary pageResources = pageDict.GetAsDictionary(PdfName.Resources);
PdfDictionary pageXObjects = pageResources.GetAsDictionary(PdfName.XObject);
var ks = pageXObjects.KeySet();
PdfName imgRef = pageXObjects.KeySet().First();
PdfStream imgStream = pageXObjects.GetAsStream(imgRef);
PdfImageXObject imgObject = new PdfImageXObject((PdfStream)imgStream.CopyTo(resultDoc));
Image image = new Image(imgObject);
image.ScaleToFit(14400, 14400);
image.SetFixedPosition(0, 0);
srcDoc.Close();
PageSize pageSize = new PageSize(image.GetImageScaledWidth(), image.GetImageScaledHeight());
Document doc = new Document(resultDoc, pageSize);
doc.Add(image);
doc.Close();
}
public static void MainBox()
{
new ImageExtractor7().ManipulatePdf(DEST);
}
protected void ManipulatePdfBox(String dest)
{
PDDocument document = null;
document = PDDocument.load(SRC);
java.util.List pages = document.getDocumentCatalog().getAllPages();
Iterator iter = pages.iterator();
while (iter.hasNext())
{
PDPage page = (PDPage)iter.next();
PDResources resources = page.getResources();
Map pageImages = resources.getImages();
if (pageImages != null)
{
Iterator imageIter = pageImages.keySet().iterator();
while (imageIter.hasNext())
{
String key = (String)imageIter.next();
PDXObjectImage image = (PDXObjectImage)pageImages.get(key);
//image.write2OutputStream(/* some output stream */);
}
}
}
}
}
}
-93
View File
@@ -1,93 +0,0 @@
using System;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using Pastel;
namespace ComicCompressor
{
public class Logger
{
public LogLevel LoggingLevel { get; set; } = LogLevel.Warning;
public bool Debug = false;
// Set to null for stdout
public string LogFile { get; set; } = null;
public void Log(object message, LogLevel logLevel, Color? col = null)
{
Log(message.ToString(), logLevel, col:col);
}
public void Log(string message, LogLevel logLevel,bool newLine = true, Color? col = null)
{
if (logLevel > LoggingLevel) return;
if (LogFile == null)
{
if(newLine)
Console.WriteLine(message.Pastel(col ?? Color.FromArgb(0,204,204,204)));
else
Console.Write(message.Pastel(col ?? Color.FromArgb(0, 204, 204, 204)));
return;
}
WriteToFile(message);
}
public void LogError(string message, Color? col = null)
{
Log(message, LogLevel.Error, col:col);
}
public void LogError(object message, Color? col = null)
{
Log(message, LogLevel.Error, col:col);
}
public void LogDebug(string message, LogLevel logLevel, Color? col = null)
{
if (!Debug) return;
Log(message, logLevel,col:col);
}
public void LogDebug(object message, LogLevel logLevel, Color? col = null)
{
LogDebug(message.ToString(), logLevel, col:col);
}
[MethodImpl(MethodImplOptions.Synchronized)]
private void WriteToFile(string message)
{
if (LogFile == null) return;
if (!Path.IsPathRooted(LogFile) && !LogFile.StartsWith("."))
{
LogFile = Path.Join(".", LogFile);
}
try
{
Directory.CreateDirectory(Path.GetDirectoryName(LogFile));
File.AppendAllText(Path.GetFullPath(LogFile), message + "\n");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Error: User does not have permissions for log file: " + LogFile);
return;
}
catch (Exception)
{
Console.WriteLine("Error: could not write to log file: " + LogFile);
return;
}
}
}
public enum LogLevel
{
Error = 2, Warning = 3, Info = 4, Verbose = 5
}
}
-631
View File
@@ -1,631 +0,0 @@
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;
//using XnaColor = Microsoft.Xna.Framework.Graphics.Color;
//using XnaRectangle = Microsoft.Xna.Framework.Rectangle;
using SysColor = System.Drawing.Color;
using SysRectangle = System.Drawing.Rectangle;
namespace ComicCompressor
{
/// <summary>
/// This class provides a managed C# abstraction for Portable Bit Map (.pbm), Portable Grey Map (.pgm) and
/// Portable Pixel Map (.ppm)images. The class is able to handle P1 (ASCII .pbm), P2 (ASCII .pgm) P3 (ASCII .ppm),
/// P5 (binary .pgm), and P6 (binary .ppm) files and streams. P4 (binary .pbm) is not supported. The class can
/// process normal images, which have widths that are multiples of 4 pixels wide, and can also handle "off-size"
/// images using a less efficient algorithm. It presents .pbm and .pgm images using the 8bppIndexed pixel format,
/// and presents .ppm images using the 24bppRGB pixel format. It can convert these images into a visually pleasing
/// greyscale bitmap image (in a less efficient 24bppRGB pixel format), which is accessible using the GreyMap
/// property.
///
/// To use this class:
/// 1. Copy the PixelMap.cs file into your own project.
/// 2. Change the namespace of the class (above this summary) from "PixelMap" to the namespace of your project.
/// 3. Compile.
///
/// This class is used by the PixelMapViewer application.
/// </summary>
public class PixelMap
{
private PixelMapHeader header;
/// <summary>
/// The header portion of the PixelMap.
/// </summary>
public PixelMapHeader Header
{
get { return header; }
set { header = value; }
}
private byte[] imageData;
/// <summary>
/// The data portion of the PixelMap.
/// </summary>
public byte[] ImageData
{
get { return imageData; }
set { imageData = value; }
}
private PixelFormat pixelFormat;
/// <summary>
/// The pixel format used by the BitMap generated by this PixelMap.
/// </summary>
public PixelFormat PixelFormat
{
get { return pixelFormat; }
}
private int bytesPerPixel;
/// <summary>
/// The number of bytes per pixel.
/// </summary>
public int BytesPerPixel
{
get { return bytesPerPixel; }
}
private int stride;
/// <summary>
/// The stride of the scan across the image. Typically this is width * bytesPerPixel, and is a multiple of 4.
/// </summary>
public int Stride
{
get { return stride; }
set { stride = value; }
}
private Bitmap bitmap;
/// <summary>
/// The Bitmap created from the PixelMap.
/// </summary>
public Bitmap BitMap
{
get { return bitmap; }
}
/// <summary>
/// A 24bpp Grey map (really only 8bpp of information, since R,G,B bytes are identical) created from the PixelMap.
/// </summary>
public Bitmap GreyMap
{
get { return CreateGreyMap(); }
}
/// <summary>
/// File-based constructor.
/// </summary>
/// <param name="filename">The name of the .pbm, .pbm, or .ppm file.</param>
public PixelMap(string filename)
{
if (File.Exists(filename))
{
FileStream stream = new FileStream(filename, FileMode.Open);
this.FromStream(stream);
stream.Close();
}
else
{
throw new FileNotFoundException("The file " + filename + " does not exist", filename);
}
}
/// <summary>
/// Stream-based constructor. Typically, the stream will be a FileStream, but it may also be a MemoryStream
/// or other object derived from the Stream class.
/// </summary>
/// <param name="stream">A stream containing the header and data portions of the .pbm, .pgm, or .ppm image.</param>
public PixelMap(Stream stream)
{
this.FromStream(stream);
}
private void FromStream(Stream stream)
{
int index;
this.header = new PixelMapHeader();
int headerItemCount = 0;
stream.Position = 0;
BinaryReader binReader = new BinaryReader(stream, Encoding.ASCII);
try
{
//1. Read the Header.
while (headerItemCount < 4)
{
char nextChar = (char)binReader.PeekChar();
if (nextChar == '#') // comment
{
while (binReader.ReadChar() != '\n') ; // ignore the rest of the line.
}
else if (Char.IsWhiteSpace(nextChar)) // whitespace
{
binReader.ReadChar(); // ignore whitespace
}
else
{
switch (headerItemCount)
{
case 0: // next item is Magic Number
// Read the first 2 characters and determine the type of pixelmap.
char[] chars = binReader.ReadChars(2);
this.header.MagicNumber = chars[0].ToString() + chars[1].ToString();
headerItemCount++;
break;
case 1: // next item is the width.
this.header.Width = ReadValue(binReader);
headerItemCount++;
break;
case 2: // next item is the height.
this.header.Height = ReadValue(binReader);
headerItemCount++;
break;
case 3: // next item is the depth.
if (this.header.MagicNumber == "P1" | this.header.MagicNumber == "P4")
{
// no depth value for PBM type.
headerItemCount++;
}
else
{
this.header.Depth = ReadValue(binReader);
headerItemCount++;
}
break;
default:
throw new Exception("Error parsing the file header.");
}
}
}
// 2. Read the image data.
// 2.1 Size the imageData array to hold the image bytes.
switch (this.header.MagicNumber)
{
case "P1": // 1 byte per pixel
this.pixelFormat = PixelFormat.Format8bppIndexed;
this.bytesPerPixel = 1;
break;
case "P2": // 1 byte per pixel
this.pixelFormat = PixelFormat.Format8bppIndexed;
this.bytesPerPixel = 1;
break;
case "P3": // 3 bytes per pixel
this.pixelFormat = PixelFormat.Format24bppRgb;
this.bytesPerPixel = 3;
break;
case "P4":
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;
break;
case "P6": // 3 bytes per pixel
this.pixelFormat = PixelFormat.Format24bppRgb;
this.bytesPerPixel = 3;
break;
default:
throw new Exception("Unknown Magic Number: " + this.header.MagicNumber);
}
this.imageData = new byte[this.header.Width * this.header.Height * this.bytesPerPixel];
this.stride = this.header.Width * this.bytesPerPixel;
//while ((this.stride % 4) != 0)
//{
// this.stride++;
//}
if (this.header.MagicNumber == "P1" | this.header.MagicNumber == "P2" | this.header.MagicNumber == "P3") // ASCII Encoding
{
int charsLeft = (int)(binReader.BaseStream.Length - binReader.BaseStream.Position);
char[] charData = binReader.ReadChars(charsLeft); // read all the data into an array in one go, for efficiency.
string valueString = string.Empty;
index = 0;
for (int i = 0; i < charData.Length; i++)
{
if (Char.IsWhiteSpace(charData[i])) // value is ignored if empty, or converted to byte and added to array otherwise.
{
if (valueString != string.Empty)
{
this.imageData[index] = (byte)int.Parse(valueString);
valueString = string.Empty;
index++;
}
}
else // add the character to the end of the valueString.
{
valueString += charData[i];
}
}
}
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);
this.imageData = binReader.ReadBytes(bytesLeft);
}
// 3. We need to change the byte order of this.imageData from BGR to RGB.
ReorderBGRtoRGB();
// 4. Create the BitMap
if (this.stride % 4 == 0)
{
this.bitmap = CreateBitMap();
}
else
{
this.bitmap = CreateBitmapOffSize();
}
// 5. Rotate the BitMap by 180 degrees so it is in the same orientation as the original image.
this.bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
}
// If the end of the stream is reached before reading all of the expected values raise an exception.
catch (EndOfStreamException e)
{
Console.WriteLine(e.Message);
throw new Exception("Error reading the stream! ", e);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw new Exception("Error reading the stream! ", ex);
}
finally
{
binReader.Close();
}
}
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<byte>.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;
}
/// <summary>
/// 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.
/// </summary>
private void ReorderBGRtoRGB()
{
byte[] tempData = new byte[this.imageData.Length];
for (int i = 0; i < this.imageData.Length; i++)
{
tempData[i] = this.imageData[this.imageData.Length - 1 - i];
}
this.imageData = tempData;
}
private int ReadValue(BinaryReader binReader)
{
string value = string.Empty;
while (!Char.IsWhiteSpace((char)binReader.PeekChar()))
{
value += binReader.ReadChar().ToString();
}
binReader.ReadByte(); // get rid of the whitespace.
return int.Parse(value);
}
private Bitmap CreateBitMap()
{
IntPtr pImageData = Marshal.AllocHGlobal(this.imageData.Length);
Marshal.Copy(this.imageData, 0, pImageData, this.imageData.Length);
Bitmap bitmap = new Bitmap(this.header.Width, this.header.Height, this.stride, this.pixelFormat, pImageData);
return bitmap;
}
private Bitmap CreateGreyMap()
{
byte[] greyData = new byte[this.header.Width * this.header.Height * 3];
int stride = this.header.Width * 3;
//while (stride % 4 != 0)
//{
// stride++;
//}
if (stride % 4 != 0)
{
return CreateGreyMapOffSize(greyData);
}
byte grey;
switch (this.pixelFormat)
{
case PixelFormat.Format24bppRgb:
int red, green, blue;
for (int i = 0; i < this.imageData.Length; i = i + 3)
{
red = (int)this.imageData[i];
green = (int)this.imageData[i + 1];
blue = (int)this.imageData[i + 2];
grey = (byte)((red + green + blue) / 3);
greyData[i] = grey;
greyData[i + 1] = grey;
greyData[i + 2] = grey;
}
break;
case PixelFormat.Format8bppIndexed:
int index = 0;
for (int i = 0; i < this.imageData.Length; i++)
{
index = 3 * i;
grey = this.imageData[i];
if (this.header.MagicNumber == "P1")
{
if ((int)grey == 1)
{
grey = (byte)255;
}
}
greyData[index] = grey;
greyData[index + 1] = grey;
greyData[index + 2] = grey;
}
break;
default:
throw new Exception("Unsupported Pixel Format: " + this.pixelFormat.ToString());
}
IntPtr pGreyData = Marshal.AllocHGlobal(greyData.Length);
Marshal.Copy(greyData, 0, pGreyData, greyData.Length);
Bitmap bitmap = new Bitmap(this.header.Width, this.header.Height, stride, PixelFormat.Format24bppRgb, pGreyData);
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
return bitmap;
}
/// <summary>
/// This method is able to handle the process of creating Bitmaps that are not sized in widths that are multiples
/// of 4 pixels (which is needed for the rapid drawing using the Marshal class.) Unfortunately, this is a very slow
/// method, since it uses SetPixel().
/// </summary>
/// <returns></returns>
private Bitmap CreateBitmapOffSize()
{
Bitmap bitmap = new Bitmap(this.header.Width, this.header.Height, PixelFormat.Format24bppRgb);
SysColor sysColor = new SysColor();
int red, green, blue, grey;
int index;
for (int x = 0; x < this.header.Width; x++)
{
for (int y = 0; y < this.header.Height; y++)
{
index = x + y * this.header.Width;
switch (this.header.MagicNumber)
{
case "P1":
grey = (int)this.imageData[index];
if (grey == 1)
{
grey = 255;
}
sysColor = SysColor.FromArgb(grey, grey, grey);
break;
case "P2":
grey = (int)this.imageData[index];
sysColor = SysColor.FromArgb(grey, grey, grey);
break;
case "P3":
index = 3 * index;
blue = (int)this.imageData[index];
green = (int)this.imageData[index + 1];
red = (int)this.imageData[index + 2];
sysColor = SysColor.FromArgb(red, green, blue);
break;
case "P4":
break;
case "P5":
grey = (int)this.imageData[index];
sysColor = SysColor.FromArgb(grey, grey, grey);
break;
case "P6":
index = 3 * index;
blue = (int)this.imageData[index];
green = (int)this.imageData[index + 1];
red = (int)this.imageData[index + 2];
sysColor = SysColor.FromArgb(red, green, blue);
break;
default:
break;
}
bitmap.SetPixel(x, y, sysColor);
}
}
return bitmap;
}
private Bitmap CreateGreyMapOffSize(byte[] greyData)
{
Bitmap bitmap = new Bitmap(this.header.Width, this.header.Height, PixelFormat.Format24bppRgb);
SysColor sysColor = new SysColor();
int index = 0;
int x = 0;
int y = -1;
switch (this.pixelFormat)
{
case PixelFormat.Format24bppRgb:
int red, green, blue, grey;
for (int i = 0; i < this.imageData.Length; i = i + 3)
{
index = i / 3; // the current pixel
if (index%this.header.Width == 0) y++; // the current row = y;
x = index - y * this.header.Width; // the current column = x;
red = (int)this.imageData[i];
green = (int)this.imageData[i + 1];
blue = (int)this.imageData[i + 2];
grey = (byte)((red + green + blue) / 3);
sysColor = SysColor.FromArgb(grey, grey, grey);
bitmap.SetPixel(x, y, sysColor);
}
break;
case PixelFormat.Format8bppIndexed:
for (int i = 0; i < this.imageData.Length; i++)
{
index = i; // the current pixel
if (index % this.header.Width == 0) y++; // the current row = y;
x = index - y * this.header.Width; // the current column = x;
grey = this.imageData[i];
if (this.header.MagicNumber == "P1")
{
if ((int)grey == 1)
{
grey = (byte)255;
}
}
sysColor = SysColor.FromArgb(grey, grey, grey);
bitmap.SetPixel(x, y, sysColor);
}
break;
default:
throw new Exception("Unsupported Pixel Format: " + this.pixelFormat.ToString());
}
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
return bitmap;
}
/// <summary>
/// This struct contains the objects that are found in the header of .pbm, .pgm, and .ppm files.
/// </summary>
[Serializable]
public struct PixelMapHeader
{
private string magicNumber;
/// <summary>
/// The "Magic Number" that identifies the type of Pixelmap. P1 = PBM (ASCII); P2 = PGM (ASCII); P3 = PPM (ASCII); P4 is not used;
/// P5 = PGM (Binary); P6 = PPM (Binary).
/// </summary>
public string MagicNumber
{
get { return magicNumber; }
set { magicNumber = value; }
}
private int width;
/// <summary>
/// The width of the image.
/// </summary>
public int Width
{
get { return width; }
set { width = value; }
}
private int height;
/// <summary>
/// The height of the image.
/// </summary>
public int Height
{
get { return height; }
set { height = value; }
}
private int depth;
/// <summary>
/// The depth (maximum color value in each channel) of the image. This allows the format to represent
/// more than a single byte (0-255) for each color channel.
/// </summary>
public int Depth
{
get { return depth; }
set { depth = value; }
}
}
}
}
+24 -1
View File
@@ -35,6 +35,10 @@ using Xabe.FFmpeg.Downloader;
using System.Reflection;
using Microsoft.VisualBasic;
using BinaryKits.Zpl.Viewer;
using iMGcompress_core;
namespace ComicCompressor
{
@@ -125,8 +129,27 @@ namespace ComicCompressor
// static Color randomColor = Color.FromKnownColor(randomColorName);
public Color[] myColors = null;
//static void TestPar2()
//{
// var items = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// // Create a SingleItemPartitioner from the collection
// var partitioner = new Partitioner<int>(items);
// // Use Parallel.ForEach to process items in parallel
// Parallel.ForEach(partitioner, item =>
// {
// Console.WriteLine($"Processing item: {item}");
// });
//}
private void OnExecute()
{
File_statistics.Clear();
@@ -141,7 +164,7 @@ namespace ComicCompressor
Stopwatch sw = Stopwatch.StartNew();
Logger = new Logger();
Logger.Debug = true;
Logger.DebugOn = true;
Logger.LoggingLevel = LogLevel.Verbose;
Logger.Log(progVersion, LogLevel.Verbose, true);
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net8.0\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
</Project>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<History>True|2025-03-12T20:02:41.5913059Z||;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>
BIN
View File
Binary file not shown.