Compare commits
10 Commits
c6c46775c4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a39b2bd253 | |||
| 57e3d6ed45 | |||
| 94f3fab41c | |||
| 7661cf506d | |||
| c58f13d77a | |||
| 4cc0732a8a | |||
| 89812c7fa6 | |||
| 1093ef6edf | |||
| 26c1b4124e | |||
| 5152a87ffa |
@@ -0,0 +1,75 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Solution Structure
|
||||||
|
|
||||||
|
Three projects in `ComicCompress.sln`, all targeting .NET 8.0:
|
||||||
|
|
||||||
|
- **ComicCompress** (this directory) — Console application entry point. `Program.cs` handles CLI parsing via `McMaster.Extensions.CommandLineUtils` and orchestrates processing.
|
||||||
|
- **iMGcompress-core** (`../iMGcompress-core`) — Class library containing all compression logic. Both the CLI and GUI depend on this.
|
||||||
|
- **iMGcompress** (`../iMGcompress`) — Windows Forms GUI (`net8.0-windows`) wrapping the core library.
|
||||||
|
|
||||||
|
## Build & Run
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Development build
|
||||||
|
dotnet build
|
||||||
|
|
||||||
|
# Run CLI (from project root)
|
||||||
|
dotnet run -- --help
|
||||||
|
dotnet run -- -i <input> -o <output> -q 75 -p
|
||||||
|
|
||||||
|
# Publish self-contained Windows executable
|
||||||
|
dotnet publish -r win-x64 -c Release
|
||||||
|
```
|
||||||
|
|
||||||
|
`launchSettings.json` defines named profiles (e.g. `PROD`, `DEV_HOME`, `Compress Image to webp`) for common invocations.
|
||||||
|
|
||||||
|
## CLI Flags
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `-i` | Input file or folder (required) |
|
||||||
|
| `-o` | Output base path (default: `converted_comics`) |
|
||||||
|
| `-r` | Recurse into subfolders |
|
||||||
|
| `-s` | Skip if output already exists |
|
||||||
|
| `-q` | WebP quality 0–100 (default: 75) |
|
||||||
|
| `-p` | Parallel processing (all CPUs) |
|
||||||
|
| `-c` | Parallel with N CPUs |
|
||||||
|
| `-d` | PDF-only mode |
|
||||||
|
| `-j` | Image-only mode |
|
||||||
|
| `-n` | Zebra PRN mode (generates PNG + WebP Base64 URI) |
|
||||||
|
| `-a` | Copy unprocessed files to output |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Data flow:** Input (`.cbr`/`.cbz` archives or loose images) → extract → convert each image to WebP → repack as `.cbz`.
|
||||||
|
|
||||||
|
### Core library (`iMGcompress-core`)
|
||||||
|
|
||||||
|
- **`Compressor.cs`** — Central engine. Reads RAR/ZIP archives via `SharpCompress`, converts images to WebP via `Imazen.WebP`, and writes output `.cbz`. Handles solid and non-solid archives separately. The first image in an archive (cover) is preserved without conversion.
|
||||||
|
- **`FileProcessor.cs`** — Orchestrates parallel or sequential processing over a file list. Inner class `ProcessorFlags` carries all options. Writes the final compression report.
|
||||||
|
- **`FileParser.cs`** — Recursively enumerates input files.
|
||||||
|
- **`ImageExtractor.cs`** — Implements `IRenderListener` (iText) to extract images from PDF pages and produce a `.cbz`.
|
||||||
|
- **`PixelMap.cs`** — Parses Portable Bitmap formats (PBM/PGM/PPM, variants P1–P6) into `System.Drawing.Bitmap` before WebP encoding.
|
||||||
|
- **`Logger.cs`** — Thread-safe colored console/file logger. `LogLevel`: Error, Warning, Info, Verbose.
|
||||||
|
- **`file_stat.cs`** — Simple DTO tracking per-file uncompressed/compressed sizes for statistics.
|
||||||
|
|
||||||
|
### Console app (`ComicCompress`)
|
||||||
|
|
||||||
|
`Program.cs` parses flags, builds a `ProcessorFlags`, calls `FileParser` to enumerate targets, then hands off to `FileProcessor`. Video (MP4→WebM) conversion uses `Xabe.FFmpeg`. Zebra PRN rendering uses `BinaryKits.Zpl.Viewer`.
|
||||||
|
|
||||||
|
## Key Dependencies
|
||||||
|
|
||||||
|
| Package | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `SharpCompress` | Read/write RAR and ZIP archives |
|
||||||
|
| `Imazen.WebP` + `libwebp.dll` | WebP encoding (requires `libwebp.dll` in output dir) |
|
||||||
|
| `Magick.NET-Q8-x64` | Advanced image format support (TIFF, etc.) |
|
||||||
|
| `iTextSharp` / `itext` | PDF parsing |
|
||||||
|
| `Xabe.FFmpeg` | Video conversion |
|
||||||
|
| `BinaryKits.Zpl.Viewer` | Zebra PRN label rendering |
|
||||||
|
| `McMaster.Extensions.CommandLineUtils` | CLI argument parsing |
|
||||||
|
|
||||||
|
**`libwebp.dll` must be present** alongside the executable at runtime — it is checked into the repo root.
|
||||||
+33
-13
@@ -7,29 +7,49 @@
|
|||||||
<OutputName>ComicCompress</OutputName>
|
<OutputName>ComicCompress</OutputName>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<Configurations>Debug;Release;ReleaseWDbg</Configurations>
|
<Configurations>Debug;Release;ReleaseWDbg</Configurations>
|
||||||
|
<Version>3.1.0.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BinaryKits.Zpl.Viewer" Version="1.2.1" />
|
<Compile Remove="itext_pdfimage\**" />
|
||||||
<PackageReference Include="GroupDocs.Parser" Version="23.8.0" />
|
<EmbeddedResource Remove="itext_pdfimage\**" />
|
||||||
<PackageReference Include="Imazen.WebP" Version="10.0.1" />
|
<None Remove="itext_pdfimage\**" />
|
||||||
<PackageReference Include="itext7" Version="8.0.1" />
|
</ItemGroup>
|
||||||
<PackageReference Include="iTextSharp" Version="5.5.13.3" />
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Compressor.cs" />
|
||||||
|
<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.3.1" />
|
||||||
|
<PackageReference Include="Imazen.WebP" Version="11.0.0" />
|
||||||
|
<PackageReference Include="iTextSharp" Version="5.5.13.5" />
|
||||||
<PackageReference Include="LibHeifSharp" Version="3.2.0" />
|
<PackageReference Include="LibHeifSharp" Version="3.2.0" />
|
||||||
<PackageReference Include="Magick.NET-Q8-x64" Version="13.6.0" />
|
<PackageReference Include="Magick.NET-Q8-x64" Version="14.14.0" />
|
||||||
<PackageReference Include="Magick.NET.SystemDrawing" Version="7.2.2" />
|
<PackageReference Include="Magick.NET.SystemDrawing" Version="8.0.23" />
|
||||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
|
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="5.1.0" />
|
||||||
<PackageReference Include="Pastel" Version="5.1.0" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
|
<PackageReference Include="Pastel" Version="7.0.1" />
|
||||||
<PackageReference Include="Pdfbox" Version="1.1.1" />
|
<PackageReference Include="Pdfbox" Version="1.1.1" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.37.2" />
|
<PackageReference Include="sharpcompress" Version="0.49.1" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
|
<PackageReference Include="System.Drawing.Common" Version="10.0.9" />
|
||||||
<PackageReference Include="Xabe.FFmpeg" Version="5.2.6" />
|
<PackageReference Include="Xabe.FFmpeg" Version="6.0.2" />
|
||||||
<PackageReference Include="Xabe.FFmpeg.Downloader" Version="5.2.6" />
|
<PackageReference Include="Xabe.FFmpeg.Downloader" Version="6.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content CopyToOutputDirectory="PreserveNewest" Include="libwebp.dll" Link="libwebp.dll" Pack="true" PackagePath="libwebp.dll" />
|
<Content CopyToOutputDirectory="PreserveNewest" Include="libwebp.dll" Link="libwebp.dll" Pack="true" PackagePath="libwebp.dll" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\iMGcompress-core\iMGcompress-core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ActiveDebugProfile>DEV_UFF</ActiveDebugProfile>
|
<ActiveDebugProfile>ComicCompress CBx</ActiveDebugProfile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||||
|
|||||||
Binary file not shown.
@@ -5,6 +5,10 @@ VisualStudioVersion = 17.5.33530.505
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComicCompress", "ComicCompress.csproj", "{2625C71D-E4EC-4768-BF04-87AAB364916A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComicCompress", "ComicCompress.csproj", "{2625C71D-E4EC-4768-BF04-87AAB364916A}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -18,6 +22,18 @@ Global
|
|||||||
{2625C71D-E4EC-4768-BF04-87AAB364916A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{2625C71D-E4EC-4768-BF04-87AAB364916A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{2625C71D-E4EC-4768-BF04-87AAB364916A}.ReleaseWDbg|Any CPU.ActiveCfg = ReleaseWDbg|Any CPU
|
{2625C71D-E4EC-4768-BF04-87AAB364916A}.ReleaseWDbg|Any CPU.ActiveCfg = ReleaseWDbg|Any CPU
|
||||||
{2625C71D-E4EC-4768-BF04-87AAB364916A}.ReleaseWDbg|Any CPU.Build.0 = ReleaseWDbg|Any CPU
|
{2625C71D-E4EC-4768-BF04-87AAB364916A}.ReleaseWDbg|Any CPU.Build.0 = ReleaseWDbg|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
|
||||||
|
{7330E00C-6C24-44B3-9768-1604802532FB}.ReleaseWDbg|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7330E00C-6C24-44B3-9768-1604802532FB}.ReleaseWDbg|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
|
||||||
|
{990E9DC1-B842-4E0E-AD7F-00CB2EF24AF4}.ReleaseWDbg|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{990E9DC1-B842-4E0E-AD7F-00CB2EF24AF4}.ReleaseWDbg|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
-473
@@ -1,473 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Drawing;
|
|
||||||
using Imazen.WebP;
|
|
||||||
using SharpCompress.Archives;
|
|
||||||
using SharpCompress.Archives.Rar;
|
|
||||||
using SharpCompress.Archives.Zip;
|
|
||||||
using System.IO;
|
|
||||||
using SharpCompress.Common;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
public class Compressor
|
|
||||||
{
|
|
||||||
private long last_mem = 0;
|
|
||||||
private Logger Logger { get; set; }
|
|
||||||
|
|
||||||
public int Quality { get; set; } = 75;
|
|
||||||
|
|
||||||
public Compressor(Logger logger)
|
|
||||||
{
|
|
||||||
Logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Compress(string filename, string outputFile = null)
|
|
||||||
{
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
|
||||||
outputFile = Path.ChangeExtension(outputFile ?? filename, "cbz");
|
|
||||||
|
|
||||||
Process proc = Process.GetCurrentProcess();
|
|
||||||
//Logger.LogDebug($"M {proc.PrivateMemorySize64}", LogLevel.Verbose, col: Color.Green);
|
|
||||||
last_mem = proc.PrivateMemorySize64;
|
|
||||||
|
|
||||||
//Logger.Log("Processing: " + filename, LogLevel.Verbose);
|
|
||||||
|
|
||||||
IArchive archive = null;
|
|
||||||
//SharpCompress.Readers.ReaderOptions opt = new ReaderOptions()
|
|
||||||
//{
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
ArchiveType ArchiveType = ArchiveType.Zip;
|
|
||||||
using (Stream stream = File.OpenRead(filename))
|
|
||||||
{
|
|
||||||
using (var reader = ReaderFactory.Open(stream))
|
|
||||||
ArchiveType = reader.ArchiveType;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSolid = false;
|
|
||||||
//if (filename.EndsWith(".cbr"))
|
|
||||||
if (ArchiveType == ArchiveType.Rar)
|
|
||||||
{
|
|
||||||
archive = RarArchive.Open(filename);
|
|
||||||
//foreach (var entry in archive.Entries)
|
|
||||||
//{
|
|
||||||
// isSolid = isSolid || entry.IsSolid;
|
|
||||||
//}
|
|
||||||
isSolid = archive.IsSolid;
|
|
||||||
}
|
|
||||||
//else if (filename.EndsWith(".cbz"))
|
|
||||||
else if (ArchiveType == ArchiveType.Zip)
|
|
||||||
{
|
|
||||||
archive = ZipArchive.Open(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
//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);
|
|
||||||
|
|
||||||
double save = 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (double, int) ProcessReader(IArchive archive, IReader reader, string outputPath)
|
|
||||||
{
|
|
||||||
var encoder = new SimpleEncoder();
|
|
||||||
|
|
||||||
|
|
||||||
bool cover = true;
|
|
||||||
double save = 0.0;
|
|
||||||
int nop = 0;
|
|
||||||
using (var output = ZipArchive.Create())
|
|
||||||
{
|
|
||||||
var imageStreams = new List<Stream>();
|
|
||||||
|
|
||||||
while (reader.MoveToNextEntry())
|
|
||||||
{
|
|
||||||
if (!reader.Entry.IsDirectory)
|
|
||||||
{
|
|
||||||
//Console.WriteLine(reader.Entry.Key);
|
|
||||||
|
|
||||||
if (reader.Entry.Key.StartsWith("__MACOSX"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var entryStream = reader.OpenEntryStream())
|
|
||||||
{
|
|
||||||
|
|
||||||
string entry_key_lower = reader.Entry.Key.ToLower();
|
|
||||||
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"))
|
|
||||||
{
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
imageStreams.Add(ms);
|
|
||||||
entryStream.CopyTo(ms);
|
|
||||||
output.AddEntry(reader.Entry.Key, ms);
|
|
||||||
Logger.Log("o", LogLevel.Verbose, false, col: Color.PaleGreen);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
nop++;
|
|
||||||
|
|
||||||
if (cover)
|
|
||||||
{
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
imageStreams.Add(ms);
|
|
||||||
entryStream.CopyTo(ms);
|
|
||||||
output.AddEntry(reader.Entry.Key, ms);
|
|
||||||
Logger.Log("C", LogLevel.Verbose, false, col: Color.Green);
|
|
||||||
cover = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessReaderEntry(reader, entryStream, imageStreams, output, encoder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.Dispose();
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
|
||||||
|
|
||||||
output.SaveTo(outputPath, CompressionType.Deflate);
|
|
||||||
|
|
||||||
//output.SaveTo(outputPath, CompressionType.None);
|
|
||||||
|
|
||||||
imageStreams.ForEach(i => i.Dispose());
|
|
||||||
}
|
|
||||||
|
|
||||||
long output_length = new System.IO.FileInfo(outputPath).Length;
|
|
||||||
|
|
||||||
save = 100 * (double)output_length / (double)archive.TotalSize;
|
|
||||||
|
|
||||||
return (save, nop);
|
|
||||||
}
|
|
||||||
public int GetPlace(int value, int place)
|
|
||||||
{
|
|
||||||
return ((value % (place * 10)) - (value % place)) / place;
|
|
||||||
}
|
|
||||||
private (double, int) ProcessArchive(IArchive archive, string outputPath, string inputPath)
|
|
||||||
{
|
|
||||||
//var fileEntries = archive.Entries.Where(e => !e.IsDirectory);
|
|
||||||
var fileEntries = archive.Entries.Where(e => !e.IsDirectory).OrderBy(e => e.Key);
|
|
||||||
|
|
||||||
var encoder = new SimpleEncoder();
|
|
||||||
|
|
||||||
bool cover = true;
|
|
||||||
double save = 0.0;
|
|
||||||
int nop = 0;
|
|
||||||
using (var output = ZipArchive.Create())
|
|
||||||
{
|
|
||||||
var imageStreams = new List<Stream>();
|
|
||||||
int count = 0;
|
|
||||||
foreach (var entry in fileEntries)
|
|
||||||
{
|
|
||||||
(bool res, bool skipped) = ProcessEntry(entry, imageStreams, output, encoder, cover);
|
|
||||||
if (res)
|
|
||||||
cover = false;
|
|
||||||
count++;
|
|
||||||
if (!skipped)
|
|
||||||
nop++;
|
|
||||||
if (count % 10 == 0)
|
|
||||||
{
|
|
||||||
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());
|
|
||||||
|
|
||||||
output.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
long output_length = new System.IO.FileInfo(outputPath).Length;
|
|
||||||
|
|
||||||
save = 100 * (double)output_length / (double)archive.TotalSize;
|
|
||||||
|
|
||||||
Program.file_stat 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private (bool, bool) ProcessEntry(IArchiveEntry entry, IList<Stream> imageStreams, ZipArchive output, SimpleEncoder encoder, bool cover)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (entry.IsEncrypted)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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") &&
|
|
||||||
!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"))
|
|
||||||
{
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
imageStreams.Add(ms);
|
|
||||||
stream.CopyTo(ms);
|
|
||||||
output.AddEntry(entry.Key, ms);
|
|
||||||
Logger.Log("o", LogLevel.Verbose, false, col: Color.Green);
|
|
||||||
return (false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
return (true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Bitmap bits;
|
|
||||||
|
|
||||||
var raw_ms = new MemoryStream();
|
|
||||||
stream.CopyTo(raw_ms);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (isPPM)
|
|
||||||
{
|
|
||||||
Logger.Log("P", LogLevel.Verbose, false, col: Color.PaleGreen);
|
|
||||||
PixelMap ppm = new PixelMap(raw_ms);
|
|
||||||
//bits = ppm.Bitmap;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError("Error parsing bitmap: " + entry.Key, col: Color.Red);
|
|
||||||
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
|
|
||||||
return (false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bits.PixelFormat != System.Drawing.Imaging.PixelFormat.Format24bppRgb)
|
|
||||||
{
|
|
||||||
var newBits = ChangePixelFormat(bits);
|
|
||||||
bits.Dispose();
|
|
||||||
bits = newBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
encoder.Encode(bits, ms, Quality);
|
|
||||||
if (ms.Length < entry.Size || isPBM)
|
|
||||||
{
|
|
||||||
imageStreams.Add(ms);
|
|
||||||
output.AddEntry(entry.Key + ".webp", ms);
|
|
||||||
Logger.Log(".", LogLevel.Verbose, false, col: Color.PaleGreen);
|
|
||||||
|
|
||||||
// release not used stream
|
|
||||||
raw_ms.Dispose();
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
imageStreams.Add(raw_ms);
|
|
||||||
|
|
||||||
raw_ms.Seek(0, SeekOrigin.Begin);
|
|
||||||
//stream.Seek(0,SeekOrigin.Begin);
|
|
||||||
output.AddEntry(entry.Key, raw_ms);
|
|
||||||
Logger.Log("=", LogLevel.Verbose, false, col: Color.Green);
|
|
||||||
|
|
||||||
// release not used stream
|
|
||||||
ms.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError("Error encoding entry: " + entry.Key, col: Color.Red);
|
|
||||||
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
bits.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError("Error Archive Extraction: " + entry.Key, col: Color.Red);
|
|
||||||
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
|
|
||||||
return (false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Process proc = Process.GetCurrentProcess();
|
|
||||||
//Logger.LogDebug($"M {proc.PrivateMemorySize64} - D {proc.PrivateMemorySize64 - last_mem}", LogLevel.Verbose);
|
|
||||||
//last_mem = proc.PrivateMemorySize64;
|
|
||||||
return (false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessReaderEntry(IReader reader, EntryStream entry, IList<Stream> imageStreams, ZipArchive output, SimpleEncoder encoder)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var stream = entry;
|
|
||||||
// using (var stream = entry.OpenEntryStream())
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
Bitmap bits;
|
|
||||||
|
|
||||||
var raw_ms = new MemoryStream();
|
|
||||||
imageStreams.Add(raw_ms);
|
|
||||||
stream.CopyTo(raw_ms);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bits = new Bitmap(raw_ms);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError("Error parsing bitmap: " + reader.Entry.Key, col: Color.Red);
|
|
||||||
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bits.PixelFormat != System.Drawing.Imaging.PixelFormat.Format24bppRgb)
|
|
||||||
{
|
|
||||||
var newBits = ChangePixelFormat(bits);
|
|
||||||
bits.Dispose();
|
|
||||||
bits = newBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
encoder.Encode(bits, ms, Quality);
|
|
||||||
if (ms.Length < reader.Entry.Size)
|
|
||||||
{
|
|
||||||
imageStreams.Add(ms);
|
|
||||||
output.AddEntry(reader.Entry.Key + ".webp", ms);
|
|
||||||
Logger.Log("s", LogLevel.Verbose, false, col: Color.Green);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
raw_ms.Seek(0, SeekOrigin.Begin);
|
|
||||||
//stream.Seek(0,SeekOrigin.Begin);
|
|
||||||
output.AddEntry(reader.Entry.Key, raw_ms);
|
|
||||||
Logger.Log("=", LogLevel.Verbose, false, col: Color.Green);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError("Error encoding entry: " + reader.Entry.Key, col: Color.Red);
|
|
||||||
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
bits.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError("Error Archive Extraction: " + reader.Entry.Key, col: Color.Red);
|
|
||||||
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
gr.DrawImage(orig, new Rectangle(0, 0, clone.Width, clone.Height));
|
|
||||||
}
|
|
||||||
|
|
||||||
return clone;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CompressPdf(string filename, string outputFile = null)
|
|
||||||
{
|
|
||||||
new ImageExtractor(Logger).ExtractImagesFromFile(filename, outputFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
# HANDOFF.md
|
||||||
|
|
||||||
|
Current state of the comic compression toolchain as of 2026-06-28.
|
||||||
|
|
||||||
|
## Repositories
|
||||||
|
|
||||||
|
| Repo | Branch | Latest commit |
|
||||||
|
|------|--------|---------------|
|
||||||
|
| `comic-compress-new` (CLI) | main | `94f3fab` |
|
||||||
|
| `iMGcompress-core` (library) | master | `a03d0e5` |
|
||||||
|
| `iMGcompress` (GUI) | master | `7b7021a` |
|
||||||
|
|
||||||
|
## What was done in this session (v3.1.0 / v2.0.0)
|
||||||
|
|
||||||
|
### NuGet package updates (all three projects)
|
||||||
|
- SharpCompress `0.42` → `0.49.1` (CVE GHSA-6c8g-7p36-r338 fixed)
|
||||||
|
- Magick.NET-Q8-x64 → `14.14.0` (multiple CVEs fixed)
|
||||||
|
- Imazen.WebP → `11.0.0`
|
||||||
|
- Imageflow.AllPlatforms + Imageflow.Net → `0.15.1`
|
||||||
|
- iTextSharp → `5.5.13.5`, itext → `9.6.0`
|
||||||
|
- McMaster.Extensions.CommandLineUtils → `5.1.0`
|
||||||
|
- System.Drawing.Common → `10.0.9`
|
||||||
|
|
||||||
|
### SharpCompress 0.49 breaking API migration (`iMGcompress-core`)
|
||||||
|
- `ReaderFactory.Open` → `OpenReader(stream, new ReaderOptions())`
|
||||||
|
- `RarArchive/ZipArchive.Open` → `OpenArchive(filename, new ReaderOptions())`
|
||||||
|
- `ZipArchive.Create()` → `CreateArchive()`
|
||||||
|
- `SaveTo(path, CompressionType)` → `SaveTo(path, new ZipWriterOptions(CompressionType))`
|
||||||
|
- Return type `ZipArchive` → `IWritableArchive<ZipWriterOptions>`
|
||||||
|
- Removed `using ZstdSharp` (internalized in 0.49)
|
||||||
|
|
||||||
|
### Parallel within-archive compression (`iMGcompress-core`, `-P` flag)
|
||||||
|
- New `ProcessArchivePAR2` + `CompressEntrySync` in `Compressor.cs`
|
||||||
|
- Batched 3-phase design: sequential read → `Parallel.For` (`ProcessorCount/2` threads) → sequential write
|
||||||
|
- Ordering guaranteed by indexed pre-allocated result slots
|
||||||
|
- Measured speedup: **~3.4–5×** vs serial on i7-12700KF (10 batch size)
|
||||||
|
- Memory bounded per batch; logs strictly in original entry order from write phase
|
||||||
|
|
||||||
|
### MemoryStream pre-sizing (`iMGcompress-core`)
|
||||||
|
- All `new MemoryStream()` → `new MemoryStream(entry.Size > 0 ? (int)entry.Size : 4096)`
|
||||||
|
- Eliminates buffer-doubling reallocations on all 9 allocation sites in `Compressor.cs`
|
||||||
|
|
||||||
|
### CLI fixes and new flags (`ComicCompress`)
|
||||||
|
- Fixed missing `OnExecuteAsync` — app was throwing `InvalidOperationException` on launch
|
||||||
|
- Added `-w|--new-webp`: use Imageflow encoder (experimental; benchmarked 8–15% slower than Imazen.WebP)
|
||||||
|
- Added `-P|--par-archive`: enable parallel within-archive compression
|
||||||
|
|
||||||
|
### GUI (`iMGcompress`)
|
||||||
|
- Added `checkBox_parArchive` (flat-button, location `382,231`, TabIndex 14)
|
||||||
|
- Default ON for COMIX and COMIX+PDF modes; OFF for IMAGE and ZEBRA
|
||||||
|
- Wired into `FileProcessor.ProcessorFlags` constructor
|
||||||
|
- Version bumped to `2.0.0 (28/06/2026)`
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- Added `CLAUDE.md` to `comic-compress-new` with build commands and architecture notes
|
||||||
|
|
||||||
|
## Current versions
|
||||||
|
|
||||||
|
- CLI (`ComicCompress`): `3.1.0 (28/06/2026)` — see `Program.cs`
|
||||||
|
- GUI (`iMGcompress`): `2.0.0 (28/06/2026)` — see `MGCompress.cs`
|
||||||
|
|
||||||
|
## Known state / open items
|
||||||
|
|
||||||
|
- **Imageflow encoder** (`-w` flag) is wired and working but consistently slower than Imazen.WebP across all tested archive sizes. Kept as an experimental path only.
|
||||||
|
- **`libwebp.dll`** must be present alongside the executable — it is checked into the repo root of both `comic-compress-new` and `iMGcompress-core`.
|
||||||
|
- The GUI runs single-threaded on the UI thread (`button_start_Click` is synchronous). Long jobs block the window. Not addressed in this session.
|
||||||
@@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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 */);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+76
-46
@@ -11,9 +11,9 @@ using System.Drawing;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using GroupDocs.Parser;
|
//using GroupDocs.Parser;
|
||||||
using GroupDocs.Parser.Data;
|
//using GroupDocs.Parser.Data;
|
||||||
using iText.Kernel.Pdf;
|
//using iText.Kernel.Pdf;
|
||||||
|
|
||||||
using Imazen.WebP;
|
using Imazen.WebP;
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ using Xabe.FFmpeg;
|
|||||||
using SharpCompress.Archives;
|
using SharpCompress.Archives;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using itext.pdfimage.Extensions;
|
//using itext.pdfimage.Extensions;
|
||||||
using org.omg.PortableInterceptor;
|
using org.omg.PortableInterceptor;
|
||||||
using org.apache.pdfbox.util.@operator;
|
using org.apache.pdfbox.util.@operator;
|
||||||
using java.nio;
|
using java.nio;
|
||||||
@@ -30,18 +30,24 @@ using ImageMagick;
|
|||||||
using javax.xml.transform;
|
using javax.xml.transform;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using iText.Barcodes.Dmcode;
|
using iText.Barcodes.Dmcode;
|
||||||
using ZstdSharp;
|
|
||||||
using Xabe.FFmpeg.Downloader;
|
using Xabe.FFmpeg.Downloader;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.VisualBasic;
|
using Microsoft.VisualBasic;
|
||||||
using BinaryKits.Zpl.Viewer;
|
using BinaryKits.Zpl.Viewer;
|
||||||
|
using iMGcompress_core;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
using iMGcompress_core;
|
||||||
|
using Compressor = iMGcompress_core.Compressor;
|
||||||
|
|
||||||
namespace ComicCompressor
|
namespace ComicCompressor
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
|
|
||||||
// string progVersion = "Version 2.0.0 - (04/07/2024)";
|
// string progVersion = "Version 2.0.0 - (04/07/2024)";
|
||||||
|
|
||||||
// Added copy of non image files with same name
|
// Added copy of non image files with same name
|
||||||
//string progVersion = "Version 2.0.1 - (12/08/2024)";
|
//string progVersion = "Version 2.0.1 - (12/08/2024)";
|
||||||
@@ -50,8 +56,23 @@ namespace ComicCompressor
|
|||||||
//string progVersion = "Version 2.0.2 - (27/08/2024)";
|
//string progVersion = "Version 2.0.2 - (27/08/2024)";
|
||||||
|
|
||||||
// Completed support for ZEBRA PRN files, generate image and base64 for prn and image. Using URI format for base64 files
|
// Completed support for ZEBRA PRN files, generate image and base64 for prn and image. Using URI format for base64 files
|
||||||
string progVersion = "Version 2.1.0 - (28/08/2024)";
|
//string progVersion = "Version 2.1.0 - (28/08/2024)";
|
||||||
|
|
||||||
|
// Moved functionalities to library iMGcompress-core
|
||||||
|
//string progVersion = "Version 3.0.0 - (21/11/2024)";
|
||||||
|
|
||||||
|
// In core changed color of percent save to white
|
||||||
|
//string progVersion = "Version 3.0.1 - (23/01/2025)";
|
||||||
|
|
||||||
|
// Updated NuGet packages (SharpCompress 0.49, Magick.NET 14.14, System.Drawing.Common 10.0,
|
||||||
|
// itext 9.6, Imazen.WebP 11.0, McMaster 5.1). SharpCompress 0.49 API migration.
|
||||||
|
// Pre-sized MemoryStream allocations with entry.Size (reduced GC pressure).
|
||||||
|
// Fixed missing OnExecuteAsync (CLI was throwing InvalidOperationException on launch).
|
||||||
|
// Added -w|--new-webp flag (Imageflow encoder; benchmarked slower than Imazen.WebP).
|
||||||
|
// Added -x|--par-archive: parallel image compression within archive (~4-5x speedup).
|
||||||
|
// Batched 3-phase: sequential read → Parallel.For (ProcessorCount/2) → sequential write.
|
||||||
|
// GUI: added Par.Archive checkbox (default ON for COMIX/COMIX+PDF modes).
|
||||||
|
string progVersion = "Version 3.1.0 - (27/06/2027)";
|
||||||
public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
|
public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
|
||||||
|
|
||||||
#region INPUT_FLAGS
|
#region INPUT_FLAGS
|
||||||
@@ -80,7 +101,6 @@ namespace ComicCompressor
|
|||||||
[Option("-c|--cpu", Description = "Run in parallel, use given number of CPUs")]
|
[Option("-c|--cpu", Description = "Run in parallel, use given number of CPUs")]
|
||||||
public int NumOfCpu { get; } = Environment.ProcessorCount;
|
public int NumOfCpu { get; } = Environment.ProcessorCount;
|
||||||
|
|
||||||
|
|
||||||
[Option("-d|--pdf", Description = "Process pdf only")]
|
[Option("-d|--pdf", Description = "Process pdf only")]
|
||||||
public bool DoPdf { get; } = false;
|
public bool DoPdf { get; } = false;
|
||||||
|
|
||||||
@@ -92,22 +112,16 @@ namespace ComicCompressor
|
|||||||
[Option("-a|--copyall", Description = "Copy all unprocessed files")]
|
[Option("-a|--copyall", Description = "Copy all unprocessed files")]
|
||||||
public bool DoCopyAll { get; } = false;
|
public bool DoCopyAll { get; } = false;
|
||||||
|
|
||||||
|
[Option("-w|--new-webp", Description = "Use Imageflow encoder (faster JPEG→WebP transcoding, no full pixel decode)")]
|
||||||
|
public bool NewWebp { get; } = false;
|
||||||
|
|
||||||
|
[Option("-x|--par-archive", Description = "Parallel image compression within each archive (~4-5x faster, uses half the CPUs)")]
|
||||||
|
public bool ParArchive { get; } = false;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region STATISTICS
|
#region STATISTICS
|
||||||
|
|
||||||
public class file_stat
|
|
||||||
{
|
|
||||||
public string Filename { get; set; }
|
|
||||||
public file_stat(string filename) {
|
|
||||||
Filename = filename;
|
|
||||||
}
|
|
||||||
public long Uncompressed_file_len { get; set; }
|
|
||||||
public long Compressed_file_len { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static Dictionary <string, file_stat> file_statistics = new Dictionary<string, file_stat>();
|
static Dictionary <string, file_stat> file_statistics = new Dictionary<string, file_stat>();
|
||||||
public static Dictionary<string, file_stat> File_statistics { get => file_statistics; set => file_statistics = value; }
|
public static Dictionary<string, file_stat> File_statistics { get => file_statistics; set => file_statistics = value; }
|
||||||
|
|
||||||
@@ -125,49 +139,65 @@ namespace ComicCompressor
|
|||||||
// static Color randomColor = Color.FromKnownColor(randomColorName);
|
// static Color randomColor = Color.FromKnownColor(randomColorName);
|
||||||
|
|
||||||
public Color[] myColors = null;
|
public Color[] myColors = null;
|
||||||
private void OnExecute()
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//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}");
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
FileProcessor.ProcessorFlags flags = new FileProcessor.ProcessorFlags(null, null,
|
||||||
|
recursive: false,
|
||||||
|
skip: false,
|
||||||
|
parallel: false,
|
||||||
|
pdf_only: false,
|
||||||
|
image_only: false,
|
||||||
|
zebra: false,
|
||||||
|
copy_all: false,
|
||||||
|
new_webp: false
|
||||||
|
);
|
||||||
|
|
||||||
|
private async Task OnExecuteAsync()
|
||||||
|
{
|
||||||
File_statistics.Clear();
|
File_statistics.Clear();
|
||||||
|
flags.New_webp = NewWebp;
|
||||||
|
flags.Par_archive = ParArchive;
|
||||||
|
|
||||||
myColors = new Color[names.Count()];
|
myColors = new Color[names.Count()];
|
||||||
for(int i = 0; i< myColors.Length; i++)
|
for (int i = 0; i < myColors.Length; i++)
|
||||||
{
|
{
|
||||||
KnownColor randomColorName = names[randomGen.Next(names.Length)];
|
KnownColor randomColorName = names[randomGen.Next(names.Length)];
|
||||||
myColors[i] = Color.FromKnownColor(randomColorName);
|
myColors[i] = Color.FromKnownColor(randomColorName);
|
||||||
}
|
}
|
||||||
//ImageExtractor7.MainBox();
|
|
||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
Logger = new Logger();
|
Logger = new Logger();
|
||||||
Logger.Debug = true;
|
Logger.DebugOn = true;
|
||||||
Logger.LoggingLevel = LogLevel.Verbose;
|
Logger.LoggingLevel = LogLevel.Verbose;
|
||||||
|
|
||||||
Logger.Log(progVersion, LogLevel.Verbose, true);
|
Logger.Log(progVersion, LogLevel.Verbose, true);
|
||||||
|
|
||||||
//var progress = new Progress<ProgressInfo>( p => Logger.Log(p.ToString(), LogLevel.Verbose, true) );
|
|
||||||
//FFmpeg.SetExecutablesPath(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
|
|
||||||
//FFmpegDownloader.GetLatestVersion(FFmpegVersion.Official,progress);
|
|
||||||
|
|
||||||
|
|
||||||
//==//FFmpeg.SetExecutablesPath (Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FFmpeg"));
|
|
||||||
|
|
||||||
//Logger.Log(FFmpeg.ExecutablesPath, LogLevel.Verbose, true);
|
|
||||||
|
|
||||||
|
|
||||||
IList<string> files = new FileParser().ListAllFiles(Input, Recursive);
|
IList<string> files = new FileParser().ListAllFiles(Input, Recursive);
|
||||||
|
|
||||||
Process(files);
|
await Process(files);
|
||||||
|
|
||||||
long time_query = sw.ElapsedMilliseconds;
|
long time_query = sw.ElapsedMilliseconds;
|
||||||
Logger.Log($"ElapsedTime {time_query} ms", LogLevel.Verbose, true);
|
Logger.Log($"ElapsedTime {time_query} ms", LogLevel.Verbose, true);
|
||||||
|
|
||||||
Console.ReadKey();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Process(IList<string> files)
|
private async Task Process(IList<string> files)
|
||||||
{
|
{
|
||||||
Compressor compressor = new Compressor(Logger);
|
Compressor compressor = new Compressor(Logger);
|
||||||
compressor.Quality = Quality;
|
compressor.Quality = Quality;
|
||||||
@@ -277,7 +307,7 @@ namespace ComicCompressor
|
|||||||
{
|
{
|
||||||
long lengthin = new System.IO.FileInfo(filename).Length;
|
long lengthin = new System.IO.FileInfo(filename).Length;
|
||||||
long lengthout = new System.IO.FileInfo(outputPath).Length;
|
long lengthout = new System.IO.FileInfo(outputPath).Length;
|
||||||
Program.file_stat fs = null;
|
file_stat fs = null;
|
||||||
Program.File_statistics.TryGetValue(filename, out fs);
|
Program.File_statistics.TryGetValue(filename, out fs);
|
||||||
if (fs != null)
|
if (fs != null)
|
||||||
{
|
{
|
||||||
@@ -294,7 +324,7 @@ namespace ComicCompressor
|
|||||||
{
|
{
|
||||||
long lengthin = new System.IO.FileInfo(filename).Length;
|
long lengthin = new System.IO.FileInfo(filename).Length;
|
||||||
long lengthout = new System.IO.FileInfo(outputPath_webp).Length;
|
long lengthout = new System.IO.FileInfo(outputPath_webp).Length;
|
||||||
Program.file_stat fs = null;
|
file_stat fs = null;
|
||||||
Program.File_statistics.TryGetValue(filename, out fs);
|
Program.File_statistics.TryGetValue(filename, out fs);
|
||||||
if (fs != null)
|
if (fs != null)
|
||||||
{
|
{
|
||||||
@@ -314,7 +344,7 @@ namespace ComicCompressor
|
|||||||
|
|
||||||
long lengthin = new System.IO.FileInfo(filename).Length;
|
long lengthin = new System.IO.FileInfo(filename).Length;
|
||||||
long lengthout = new System.IO.FileInfo(outputPath).Length;
|
long lengthout = new System.IO.FileInfo(outputPath).Length;
|
||||||
Program.file_stat fs = null;
|
file_stat fs = null;
|
||||||
Program.File_statistics.TryGetValue(filename, out fs);
|
Program.File_statistics.TryGetValue(filename, out fs);
|
||||||
if (fs != null)
|
if (fs != null)
|
||||||
{
|
{
|
||||||
@@ -400,7 +430,7 @@ namespace ComicCompressor
|
|||||||
|
|
||||||
long lengthin = new System.IO.FileInfo(filename).Length;
|
long lengthin = new System.IO.FileInfo(filename).Length;
|
||||||
long lengthout = new System.IO.FileInfo(outputPath).Length;
|
long lengthout = new System.IO.FileInfo(outputPath).Length;
|
||||||
Program.file_stat fs = null;
|
file_stat fs = null;
|
||||||
Program.File_statistics.TryGetValue(filename, out fs);
|
Program.File_statistics.TryGetValue(filename, out fs);
|
||||||
if (fs != null)
|
if (fs != null)
|
||||||
{
|
{
|
||||||
@@ -442,7 +472,7 @@ namespace ComicCompressor
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
compressor.Compress(filename, outputPath);
|
compressor.Compress(filename, outputPath, flags:flags);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -807,7 +837,7 @@ namespace ComicCompressor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
image.Dispose();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -32,6 +32,10 @@
|
|||||||
"DEV_HOME": {
|
"DEV_HOME": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "-i \"D:\\SynologyDrive\\A\\_PRINT_TEMPLATES_\\DEV_IN\" -o \"D:\\SynologyDrive\\A\\_PRINT_TEMPLATES_\\DEV_OUT\" -r -s -j -a -n"
|
"commandLineArgs": "-i \"D:\\SynologyDrive\\A\\_PRINT_TEMPLATES_\\DEV_IN\" -o \"D:\\SynologyDrive\\A\\_PRINT_TEMPLATES_\\DEV_OUT\" -r -s -j -a -n"
|
||||||
|
},
|
||||||
|
"ComicCompress CBx": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "-i _IMG_ -o _IMG_PDF_ -r -s -d"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user