From 865e29d4780dfb6bb8a8fc16bc5db1dc2921c1f2 Mon Sep 17 00:00:00 2001 From: Luca De Petrillo Date: Sun, 16 Aug 2015 13:30:26 +0200 Subject: [PATCH] Added an option to enable PDF image compression with a customizable quality: images are compressed only if they are "High Quality" (Maximum Quality profile option enabled for scan or imported from a lossless format from file), and only if the compressed image is smaller than the original one. --- .../ImportExport/Pdf/PdfImageSettings.cs | 37 ++++ NAPS2.Core/ImportExport/Pdf/PdfSettings.cs | 16 ++ .../ImportExport/Pdf/PdfSharpExporter.cs | 34 ++- NAPS2.Core/NAPS2.Core.csproj | 1 + .../Scan/Images/FileBasedScannedImage.cs | 9 + NAPS2.Core/Scan/Images/IScannedImage.cs | 7 + NAPS2.Core/Scan/Images/ScannedImage.cs | 10 + NAPS2.Core/WinForms/FPdfSettings.Designer.cs | 60 ++++++ NAPS2.Core/WinForms/FPdfSettings.cs | 42 +++- NAPS2.Core/WinForms/FPdfSettings.resx | 195 ++++++++++++++++-- 10 files changed, 389 insertions(+), 22 deletions(-) create mode 100644 NAPS2.Core/ImportExport/Pdf/PdfImageSettings.cs diff --git a/NAPS2.Core/ImportExport/Pdf/PdfImageSettings.cs b/NAPS2.Core/ImportExport/Pdf/PdfImageSettings.cs new file mode 100644 index 0000000000..1df2c9fa3d --- /dev/null +++ b/NAPS2.Core/ImportExport/Pdf/PdfImageSettings.cs @@ -0,0 +1,37 @@ +/* + NAPS2 (Not Another PDF Scanner 2) + http://sourceforge.net/projects/naps2/ + + Copyright (C) 2015 Luca De Petrillo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +using NAPS2.Lang.Resources; + +namespace NAPS2.ImportExport.Pdf +{ + public class PdfImageSettings + { + public const int DEFAULT_JPEG_QUALITY = 75; + public const bool DEFAULT_COMPRESS_IMAGES = false; + + public PdfImageSettings() + { + JpegQuality = DEFAULT_JPEG_QUALITY; + CompressImages = DEFAULT_COMPRESS_IMAGES; + } + + public int JpegQuality { get; set; } + + public bool CompressImages { get; set; } + } +} \ No newline at end of file diff --git a/NAPS2.Core/ImportExport/Pdf/PdfSettings.cs b/NAPS2.Core/ImportExport/Pdf/PdfSettings.cs index 7de16afe21..556b339cae 100644 --- a/NAPS2.Core/ImportExport/Pdf/PdfSettings.cs +++ b/NAPS2.Core/ImportExport/Pdf/PdfSettings.cs @@ -5,6 +5,7 @@ Copyright (C) 2009 Pavel Sorejs Copyright (C) 2012 Michael Adams Copyright (C) 2013 Peter De Leeuw + Copyright (C) 2015 Luca De Petrillo Copyright (C) 2012-2015 Ben Olden-Cooligan This program is free software; you can redistribute it and/or @@ -27,11 +28,13 @@ namespace NAPS2.ImportExport.Pdf public class PdfSettings { private PdfMetadata metadata; + private PdfImageSettings imageSettings; private PdfEncryption encryption; public PdfSettings() { metadata = new PdfMetadata(); + imageSettings = new PdfImageSettings(); encryption = new PdfEncryption(); } @@ -50,6 +53,19 @@ public PdfMetadata Metadata } } + public PdfImageSettings ImageSettings + { + get { return imageSettings; } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + imageSettings = value; + } + } + public PdfEncryption Encryption { get { return encryption; } diff --git a/NAPS2.Core/ImportExport/Pdf/PdfSharpExporter.cs b/NAPS2.Core/ImportExport/Pdf/PdfSharpExporter.cs index 0418f52176..cabaac4426 100644 --- a/NAPS2.Core/ImportExport/Pdf/PdfSharpExporter.cs +++ b/NAPS2.Core/ImportExport/Pdf/PdfSharpExporter.cs @@ -5,6 +5,7 @@ Copyright (C) 2009 Pavel Sorejs Copyright (C) 2012 Michael Adams Copyright (C) 2013 Peter De Leeuw + Copyright (C) 2015 Luca De Petrillo Copyright (C) 2012-2015 Ben Olden-Cooligan This program is free software; you can redistribute it and/or @@ -30,6 +31,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the using PdfSharp.Pdf; using PdfSharp.Pdf.IO; using PdfSharp.Pdf.Security; +using System.Drawing.Imaging; namespace NAPS2.ImportExport.Pdf { @@ -107,7 +109,37 @@ public bool Export(string path, IEnumerable images, PdfSettings s tf.DrawString(element.Text, new XFont("Times New Roman", adjustedFontSize, XFontStyle.Regular), XBrushes.Transparent, adjustedBounds); } } - gfx.DrawImage(img, 0, 0, (int)realWidth, (int)realHeight); + + if (scannedImage.IsHighQuality() && settings.ImageSettings.CompressImages) + { + // Compress the image to JPEG and use it if smaller than the original one. + var quality = Math.Max(Math.Min(settings.ImageSettings.JpegQuality, 100), 0); + var encoder = ImageCodecInfo.GetImageEncoders().First(x => x.FormatID == ImageFormat.Jpeg.Guid); + var encoderParams = new EncoderParameters(1); + encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, quality); + + using (var streamJpg = new MemoryStream()) + { + img.Save(streamJpg, encoder, encoderParams); + if (streamJpg.Length < stream.Length) + { + using (var imgJpg = Bitmap.FromStream(streamJpg)) + { + gfx.DrawImage(imgJpg, 0, 0, (int)realWidth, (int)realHeight); + } + } + else + { + gfx.DrawImage(img, 0, 0, (int)realWidth, (int)realHeight); + } + + } + } + else + { + gfx.DrawImage(img, 0, 0, (int)realWidth, (int)realHeight); + } + i++; } } diff --git a/NAPS2.Core/NAPS2.Core.csproj b/NAPS2.Core/NAPS2.Core.csproj index 93cea8c4f5..dc4c916cbe 100644 --- a/NAPS2.Core/NAPS2.Core.csproj +++ b/NAPS2.Core/NAPS2.Core.csproj @@ -81,6 +81,7 @@ True Icons.resx + diff --git a/NAPS2.Core/Scan/Images/FileBasedScannedImage.cs b/NAPS2.Core/Scan/Images/FileBasedScannedImage.cs index db72f3edef..f3a090c0a4 100644 --- a/NAPS2.Core/Scan/Images/FileBasedScannedImage.cs +++ b/NAPS2.Core/Scan/Images/FileBasedScannedImage.cs @@ -5,6 +5,7 @@ Copyright (C) 2009 Pavel Sorejs Copyright (C) 2012 Michael Adams Copyright (C) 2013 Peter De Leeuw + Copyright (C) 2015 Luca De Petrillo Copyright (C) 2012-2015 Ben Olden-Cooligan This program is free software; you can redistribute it and/or @@ -69,9 +70,12 @@ private static DirectoryInfo RecoveryFolder // Store a base image and transform pair (rather than doing the actual transform on the base image) // so that JPEG degradation is minimized when multiple rotations/flips are performed private readonly List transformList = new List(); + private readonly bool highQuality; public FileBasedScannedImage(Bitmap img, ScanBitDepth bitDepth, bool highQuality) { + this.highQuality = highQuality; + Bitmap baseImage; MemoryStream baseImageEncoded; ScannedImageHelper.GetSmallestBitmap(img, bitDepth, highQuality, out baseImage, out baseImageEncoded, out baseImageFileFormat); @@ -214,5 +218,10 @@ public void MovedTo(int index) _recoveryIndexManager.Index.Images.Insert(index, indexImage); _recoveryIndexManager.Save(); } + + public bool IsHighQuality() + { + return highQuality; + } } } diff --git a/NAPS2.Core/Scan/Images/IScannedImage.cs b/NAPS2.Core/Scan/Images/IScannedImage.cs index 0f950f049f..ecbdc4760f 100644 --- a/NAPS2.Core/Scan/Images/IScannedImage.cs +++ b/NAPS2.Core/Scan/Images/IScannedImage.cs @@ -5,6 +5,7 @@ Copyright (C) 2009 Pavel Sorejs Copyright (C) 2012 Michael Adams Copyright (C) 2013 Peter De Leeuw + Copyright (C) 2015 Luca De Petrillo Copyright (C) 2012-2015 Ben Olden-Cooligan This program is free software; you can redistribute it and/or @@ -77,5 +78,11 @@ public interface IScannedImage : IDisposable /// /// The index at which the image was inserted after being removed. void MovedTo(int index); + + + /// + /// Indicates if the scanned image has been acquired using "High Quality" configuration, or if it has been imported using a lossless format. + /// + bool IsHighQuality(); } } diff --git a/NAPS2.Core/Scan/Images/ScannedImage.cs b/NAPS2.Core/Scan/Images/ScannedImage.cs index 6029ae04e6..0a9b36e78d 100644 --- a/NAPS2.Core/Scan/Images/ScannedImage.cs +++ b/NAPS2.Core/Scan/Images/ScannedImage.cs @@ -5,6 +5,7 @@ Copyright (C) 2009 Pavel Sorejs Copyright (C) 2012 Michael Adams Copyright (C) 2013 Peter De Leeuw + Copyright (C) 2015 Luca De Petrillo Copyright (C) 2012-2015 Ben Olden-Cooligan This program is free software; you can redistribute it and/or @@ -34,6 +35,7 @@ public class ScannedImage : IScannedImage private Bitmap thumbnail; // The image's bit depth (or C24Bit if unknown) private readonly ScanBitDepth bitDepth; + private readonly bool highQuality; // Only one of the following (baseImage/baseImageEncoded) should have a value for any particular ScannedImage private readonly Bitmap baseImage; private readonly MemoryStream baseImageEncoded; @@ -42,9 +44,12 @@ public class ScannedImage : IScannedImage // so that JPEG degradation is minimized when multiple rotations/flips are performed private readonly List transformList = new List(); + public ScannedImage(Bitmap img, ScanBitDepth bitDepth, bool highQuality) { this.bitDepth = bitDepth; + this.highQuality = highQuality; + ScannedImageHelper.GetSmallestBitmap(img, bitDepth, highQuality, out baseImage, out baseImageEncoded, out baseImageFileFormat); } @@ -126,5 +131,10 @@ public void MovedTo(int index) { // Do nothing, this is only important for FileBasedScannedImage } + + public bool IsHighQuality() + { + return highQuality; + } } } diff --git a/NAPS2.Core/WinForms/FPdfSettings.Designer.cs b/NAPS2.Core/WinForms/FPdfSettings.Designer.cs index b07833789b..d1916d7593 100644 --- a/NAPS2.Core/WinForms/FPdfSettings.Designer.cs +++ b/NAPS2.Core/WinForms/FPdfSettings.Designer.cs @@ -67,8 +67,16 @@ private void InitializeComponent() this.linkPlaceholders = new System.Windows.Forms.LinkLabel(); this.txtDefaultFileName = new System.Windows.Forms.TextBox(); this.label1 = new System.Windows.Forms.Label(); + this.groupImage = new System.Windows.Forms.GroupBox(); + this.lbCompressImageQuality = new System.Windows.Forms.Label(); + this.cbCompressImagePdf = new System.Windows.Forms.CheckBox(); + this.lblImageCompressionInfo = new System.Windows.Forms.Label(); + this.txtJpegQuality = new System.Windows.Forms.TextBox(); + this.tbJpegQuality = new System.Windows.Forms.TrackBar(); this.groupMetadata.SuspendLayout(); this.groupProtection.SuspendLayout(); + this.groupImage.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.tbJpegQuality)).BeginInit(); this.SuspendLayout(); // // btnOK @@ -281,10 +289,53 @@ private void InitializeComponent() resources.ApplyResources(this.label1, "label1"); this.label1.Name = "label1"; // + // groupImage + // + this.groupImage.Controls.Add(this.lbCompressImageQuality); + this.groupImage.Controls.Add(this.cbCompressImagePdf); + this.groupImage.Controls.Add(this.lblImageCompressionInfo); + this.groupImage.Controls.Add(this.txtJpegQuality); + this.groupImage.Controls.Add(this.tbJpegQuality); + resources.ApplyResources(this.groupImage, "groupImage"); + this.groupImage.Name = "groupImage"; + this.groupImage.TabStop = false; + // + // lbCompressImageQuality + // + resources.ApplyResources(this.lbCompressImageQuality, "lbCompressImageQuality"); + this.lbCompressImageQuality.Name = "lbCompressImageQuality"; + // + // cbCompressImagePdf + // + resources.ApplyResources(this.cbCompressImagePdf, "cbCompressImagePdf"); + this.cbCompressImagePdf.Name = "cbCompressImagePdf"; + this.cbCompressImagePdf.UseVisualStyleBackColor = true; + this.cbCompressImagePdf.CheckedChanged += new System.EventHandler(this.cbCompressImagePdf_CheckedChanged); + // + // lblImageCompressionInfo + // + resources.ApplyResources(this.lblImageCompressionInfo, "lblImageCompressionInfo"); + this.lblImageCompressionInfo.Name = "lblImageCompressionInfo"; + // + // txtJpegQuality + // + resources.ApplyResources(this.txtJpegQuality, "txtJpegQuality"); + this.txtJpegQuality.Name = "txtJpegQuality"; + this.txtJpegQuality.TextChanged += new System.EventHandler(this.txtJpegQuality_TextChanged); + // + // tbJpegQuality + // + resources.ApplyResources(this.tbJpegQuality, "tbJpegQuality"); + this.tbJpegQuality.Maximum = 100; + this.tbJpegQuality.Name = "tbJpegQuality"; + this.tbJpegQuality.TickFrequency = 25; + this.tbJpegQuality.Scroll += new System.EventHandler(this.tbJpegQuality_Scroll); + // // FPdfSettings // resources.ApplyResources(this, "$this"); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.groupImage); this.Controls.Add(this.linkPlaceholders); this.Controls.Add(this.txtDefaultFileName); this.Controls.Add(this.label1); @@ -301,6 +352,9 @@ private void InitializeComponent() this.groupMetadata.PerformLayout(); this.groupProtection.ResumeLayout(false); this.groupProtection.PerformLayout(); + this.groupImage.ResumeLayout(false); + this.groupImage.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.tbJpegQuality)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -341,5 +395,11 @@ private void InitializeComponent() private System.Windows.Forms.LinkLabel linkPlaceholders; private System.Windows.Forms.TextBox txtDefaultFileName; private System.Windows.Forms.Label label1; + private System.Windows.Forms.GroupBox groupImage; + private System.Windows.Forms.Label lblImageCompressionInfo; + private System.Windows.Forms.TextBox txtJpegQuality; + private System.Windows.Forms.TrackBar tbJpegQuality; + private System.Windows.Forms.CheckBox cbCompressImagePdf; + private System.Windows.Forms.Label lbCompressImageQuality; } } diff --git a/NAPS2.Core/WinForms/FPdfSettings.cs b/NAPS2.Core/WinForms/FPdfSettings.cs index b017b0f583..49170ac769 100644 --- a/NAPS2.Core/WinForms/FPdfSettings.cs +++ b/NAPS2.Core/WinForms/FPdfSettings.cs @@ -5,6 +5,7 @@ Copyright (C) 2009 Pavel Sorejs Copyright (C) 2012 Michael Adams Copyright (C) 2013 Peter De Leeuw + Copyright (C) 2015 Luca De Petrillo Copyright (C) 2012-2015 Ben Olden-Cooligan This program is free software; you can redistribute it and/or @@ -24,6 +25,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the using System.Windows.Forms; using NAPS2.Config; using NAPS2.ImportExport.Pdf; +using System.Globalization; namespace NAPS2.WinForms { @@ -42,11 +44,11 @@ public FPdfSettings(PdfSettingsContainer pdfSettingsContainer, IUserConfigManage protected override void OnLoad(object sender, EventArgs e) { new LayoutManager(this) - .Bind(btnOK, btnCancel, cbShowOwnerPassword, cbShowUserPassword) + .Bind(btnOK, btnCancel, cbShowOwnerPassword, cbShowUserPassword, txtJpegQuality) .RightToForm() - .Bind(groupMetadata, groupProtection) + .Bind(groupMetadata, groupImage, groupProtection) .WidthToForm() - .Bind(txtDefaultFileName, txtTitle, txtAuthor, txtSubject, txtKeywords, txtOwnerPassword, txtUserPassword) + .Bind(txtDefaultFileName, txtTitle, txtAuthor, txtSubject, txtKeywords, txtOwnerPassword, txtUserPassword, tbJpegQuality, lblImageCompressionInfo) .WidthToForm() .Activate(); @@ -73,6 +75,9 @@ private void UpdateValues(PdfSettings pdfSettings) cbAllowFullQualityPrinting.Checked = pdfSettings.Encryption.AllowFullQualityPrinting; cbAllowDocumentModification.Checked = pdfSettings.Encryption.AllowDocumentModification; cbAllowPrinting.Checked = pdfSettings.Encryption.AllowPrinting; + + cbCompressImagePdf.Checked = pdfSettings.ImageSettings.CompressImages; + txtJpegQuality.Text = pdfSettings.ImageSettings.JpegQuality.ToString(CultureInfo.InvariantCulture); } private void UpdateEnabled() @@ -84,6 +89,10 @@ private void UpdateEnabled() cbAllowContentCopying.Enabled = cbAllowContentCopyingForAccessibility.Enabled = cbAllowDocumentAssembly.Enabled = cbAllowDocumentModification.Enabled = cbAllowFormFilling.Enabled = cbAllowFullQualityPrinting.Enabled = cbAllowPrinting.Enabled = encrypt; + + bool compressImage = cbCompressImagePdf.Checked; + txtJpegQuality.Enabled = tbJpegQuality.Enabled = lbCompressImageQuality.Enabled = + lblImageCompressionInfo.Enabled = compressImage; } private void btnOK_Click(object sender, EventArgs e) @@ -98,6 +107,11 @@ private void btnOK_Click(object sender, EventArgs e) Subject = txtSubject.Text, Keywords = txtKeywords.Text }, + ImageSettings = + { + CompressImages = cbCompressImagePdf.Checked, + JpegQuality = tbJpegQuality.Value + }, Encryption = { EncryptPdf = cbEncryptPdf.Checked, @@ -157,5 +171,27 @@ private void linkPlaceholders_LinkClicked(object sender, LinkLabelLinkClickedEve txtDefaultFileName.Text = form.FileName; } } + + private void tbJpegQuality_Scroll(object sender, EventArgs e) + { + txtJpegQuality.Text = tbJpegQuality.Value.ToString("G"); + } + + private void txtJpegQuality_TextChanged(object sender, EventArgs e) + { + int value; + if (int.TryParse(txtJpegQuality.Text, out value)) + { + if (value >= tbJpegQuality.Minimum && value <= tbJpegQuality.Maximum) + { + tbJpegQuality.Value = value; + } + } + } + + private void cbCompressImagePdf_CheckedChanged(object sender, EventArgs e) + { + UpdateEnabled(); + } } } diff --git a/NAPS2.Core/WinForms/FPdfSettings.resx b/NAPS2.Core/WinForms/FPdfSettings.resx index 3bd7e8d6da..25ee69b5af 100644 --- a/NAPS2.Core/WinForms/FPdfSettings.resx +++ b/NAPS2.Core/WinForms/FPdfSettings.resx @@ -123,7 +123,7 @@ - 264, 584 + 264, 703 75, 23 @@ -145,13 +145,13 @@ $this - 8 + 9 NoControl - 345, 584 + 345, 703 75, 23 @@ -172,7 +172,7 @@ $this - 7 + 8 17, 17 @@ -403,7 +403,7 @@ $this - 6 + 7 True @@ -823,7 +823,7 @@ 14 - 12, 253 + 12, 372 408, 304 @@ -844,13 +844,16 @@ $this - 5 + 6 True + + NoControl + - 12, 563 + 12, 682 145, 17 @@ -871,10 +874,10 @@ $this - 4 + 5 - 12, 584 + 12, 703 145, 23 @@ -895,7 +898,7 @@ $this - 3 + 4 True @@ -925,7 +928,7 @@ $this - 0 + 1 12, 25 @@ -946,7 +949,7 @@ $this - 1 + 2 True @@ -976,8 +979,164 @@ $this + 3 + + + True + + + NoControl + + + 6, 35 + + + 65, 13 + + + 39 + + + Jpeg Quality + + + lbCompressImageQuality + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupImage + + + 0 + + + True + + + 6, 15 + + + 108, 17 + + + 38 + + + Compress images + + + cbCompressImagePdf + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupImage + + + 1 + + + NoControl + + + 6, 83 + + + 396, 27 + + + 37 + + + Images are compressed if Maximum Quality profile option is used. + + + lblImageCompressionInfo + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupImage + + 2 + + 364, 51 + + + 38, 20 + + + 36 + + + 0 + + + txtJpegQuality + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupImage + + + 3 + + + NoControl + + + 6, 51 + + + 352, 45 + + + 35 + + + tbJpegQuality + + + System.Windows.Forms.TrackBar, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupImage + + + 4 + + + 12, 253 + + + 408, 113 + + + 34 + + + Image compression + + + groupImage + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + True @@ -985,7 +1144,7 @@ 6, 13 - 432, 619 + 432, 738 @@ -1012,10 +1171,10 @@ - 600, 658 + 600, 777 - 400, 658 + 400, 777 PDF Settings @@ -1024,12 +1183,12 @@ ilProfileIcons - NAPS2.WinForms.ILProfileIcons, NAPS2.Core, Version=4.0.3.7448, Culture=neutral, PublicKeyToken=null + NAPS2.WinForms.ILProfileIcons, NAPS2.Core, Version=4.1.1.22191, Culture=neutral, PublicKeyToken=null FPdfSettings - NAPS2.WinForms.FormBase, NAPS2.Core, Version=4.0.3.7448, Culture=neutral, PublicKeyToken=null + NAPS2.WinForms.FormBase, NAPS2.Core, Version=4.1.1.22191, Culture=neutral, PublicKeyToken=null \ No newline at end of file