mirror of
https://github.com/cyanfish/naps2.git
synced 2024-09-11 15:26:55 +03:00
Add ImageSaveOptions and optimize pixel format for saving
This commit is contained in:
parent
bc95170215
commit
b333d6c89b
@ -57,39 +57,43 @@ public class GdiImage : IMemoryImage
|
||||
|
||||
public ImagePixelFormat LogicalPixelFormat { get; set; }
|
||||
|
||||
public void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified, int quality = -1)
|
||||
public void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified, ImageSaveOptions? options = null)
|
||||
{
|
||||
if (imageFormat == ImageFileFormat.Unspecified)
|
||||
{
|
||||
imageFormat = ImageContext.GetFileFormatFromExtension(path);
|
||||
}
|
||||
ImageContext.CheckSupportsFormat(imageFormat);
|
||||
if (imageFormat == ImageFileFormat.Jpeg && quality != -1)
|
||||
options ??= new ImageSaveOptions();
|
||||
using var helper = PixelFormatHelper.Create(this, options.PixelFormatHint);
|
||||
if (imageFormat == ImageFileFormat.Jpeg && options.Quality != -1)
|
||||
{
|
||||
var (encoder, encoderParams) = GetJpegSaveArgs(quality);
|
||||
Bitmap.Save(path, encoder, encoderParams);
|
||||
var (encoder, encoderParams) = GetJpegSaveArgs(options.Quality);
|
||||
helper.Image.Bitmap.Save(path, encoder, encoderParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
Bitmap.Save(path, imageFormat.AsImageFormat());
|
||||
helper.Image.Bitmap.Save(path, imageFormat.AsImageFormat());
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(Stream stream, ImageFileFormat imageFormat, int quality = -1)
|
||||
public void Save(Stream stream, ImageFileFormat imageFormat, ImageSaveOptions? options = null)
|
||||
{
|
||||
if (imageFormat == ImageFileFormat.Unspecified)
|
||||
{
|
||||
throw new ArgumentException("Format required to save to a stream", nameof(imageFormat));
|
||||
}
|
||||
ImageContext.CheckSupportsFormat(imageFormat);
|
||||
if (imageFormat == ImageFileFormat.Jpeg && quality != -1)
|
||||
options ??= new ImageSaveOptions();
|
||||
using var helper = PixelFormatHelper.Create(this, options.PixelFormatHint);
|
||||
if (imageFormat == ImageFileFormat.Jpeg && options.Quality != -1)
|
||||
{
|
||||
var (encoder, encoderParams) = GetJpegSaveArgs(quality);
|
||||
Bitmap.Save(stream, encoder, encoderParams);
|
||||
var (encoder, encoderParams) = GetJpegSaveArgs(options.Quality);
|
||||
helper.Image.Bitmap.Save(stream, encoder, encoderParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
Bitmap.Save(stream, imageFormat.AsImageFormat());
|
||||
helper.Image.Bitmap.Save(stream, imageFormat.AsImageFormat());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,8 @@ public class GtkImage : IMemoryImage
|
||||
|
||||
public ImagePixelFormat LogicalPixelFormat { get; set; }
|
||||
|
||||
public void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified, int quality = -1)
|
||||
public void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified,
|
||||
ImageSaveOptions? options = null)
|
||||
{
|
||||
if (imageFormat == ImageFileFormat.Unspecified)
|
||||
{
|
||||
@ -81,12 +82,14 @@ public class GtkImage : IMemoryImage
|
||||
return;
|
||||
}
|
||||
ImageContext.CheckSupportsFormat(imageFormat);
|
||||
options ??= new ImageSaveOptions();
|
||||
var type = GetType(imageFormat);
|
||||
var (keys, values) = GetSaveOptions(imageFormat, quality);
|
||||
Pixbuf.Savev(path, type, keys, values);
|
||||
var (keys, values) = GetSaveOptions(imageFormat, options.Quality);
|
||||
using var helper = PixelFormatHelper.Create(this, options.PixelFormatHint, minFormat: ImagePixelFormat.RGB24);
|
||||
helper.Image.Pixbuf.Savev(path, type, keys, values);
|
||||
}
|
||||
|
||||
public void Save(Stream stream, ImageFileFormat imageFormat, int quality = -1)
|
||||
public void Save(Stream stream, ImageFileFormat imageFormat, ImageSaveOptions? options = null)
|
||||
{
|
||||
if (imageFormat == ImageFileFormat.Unspecified)
|
||||
{
|
||||
@ -98,10 +101,12 @@ public class GtkImage : IMemoryImage
|
||||
return;
|
||||
}
|
||||
ImageContext.CheckSupportsFormat(imageFormat);
|
||||
options ??= new ImageSaveOptions();
|
||||
var type = GetType(imageFormat);
|
||||
var (keys, values) = GetSaveOptions(imageFormat, quality);
|
||||
var (keys, values) = GetSaveOptions(imageFormat, options.Quality);
|
||||
// TODO: Map to OutputStream directly?
|
||||
stream.Write(Pixbuf.SaveToBuffer(type, keys, values));
|
||||
using var helper = PixelFormatHelper.Create(this, options.PixelFormatHint, minFormat: ImagePixelFormat.RGB24);
|
||||
stream.Write(helper.Image.Pixbuf.SaveToBuffer(type, keys, values));
|
||||
}
|
||||
|
||||
private string GetType(ImageFileFormat fileFormat) => fileFormat switch
|
||||
|
@ -117,37 +117,39 @@ public class MacImage : IMemoryImage
|
||||
|
||||
public ImagePixelFormat LogicalPixelFormat { get; set; }
|
||||
|
||||
public void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified, int quality = -1)
|
||||
public void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified,
|
||||
ImageSaveOptions? options = null)
|
||||
{
|
||||
if (imageFormat == ImageFileFormat.Unspecified)
|
||||
{
|
||||
imageFormat = ImageContext.GetFileFormatFromExtension(path);
|
||||
}
|
||||
ImageContext.CheckSupportsFormat(imageFormat);
|
||||
var rep = GetRepForSaving(imageFormat, quality);
|
||||
var rep = GetRepForSaving(imageFormat, options);
|
||||
if (!rep.Save(path, false, out var error))
|
||||
{
|
||||
throw new IOException(error!.Description);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(Stream stream, ImageFileFormat imageFormat, int quality = -1)
|
||||
public void Save(Stream stream, ImageFileFormat imageFormat, ImageSaveOptions? options = null)
|
||||
{
|
||||
if (imageFormat == ImageFileFormat.Unspecified)
|
||||
{
|
||||
throw new ArgumentException("Format required to save to a stream", nameof(imageFormat));
|
||||
}
|
||||
ImageContext.CheckSupportsFormat(imageFormat);
|
||||
var rep = GetRepForSaving(imageFormat, quality);
|
||||
var rep = GetRepForSaving(imageFormat, options);
|
||||
rep.AsStream().CopyTo(stream);
|
||||
}
|
||||
|
||||
private NSData GetRepForSaving(ImageFileFormat imageFormat, int quality)
|
||||
private NSData GetRepForSaving(ImageFileFormat imageFormat, ImageSaveOptions? options)
|
||||
{
|
||||
options ??= new ImageSaveOptions();
|
||||
lock (MacImageContext.ConstructorLock)
|
||||
{
|
||||
var props = quality != -1 && imageFormat is ImageFileFormat.Jpeg or ImageFileFormat.Jpeg2000
|
||||
? NSDictionary.FromObjectAndKey(NSNumber.FromDouble(quality / 100.0),
|
||||
var props = options.Quality != -1 && imageFormat is ImageFileFormat.Jpeg or ImageFileFormat.Jpeg2000
|
||||
? NSDictionary.FromObjectAndKey(NSNumber.FromDouble(options.Quality / 100.0),
|
||||
NSBitmapImageRep.CompressionFactor)
|
||||
: null;
|
||||
var fileType = imageFormat switch
|
||||
@ -159,18 +161,15 @@ public class MacImage : IMemoryImage
|
||||
ImageFileFormat.Jpeg2000 => NSBitmapImageFileType.Jpeg2000,
|
||||
_ => throw new InvalidOperationException("Unsupported image format")
|
||||
};
|
||||
var targetFormat = PixelFormat;
|
||||
if (imageFormat == ImageFileFormat.Bmp && targetFormat == ImagePixelFormat.Gray8)
|
||||
var targetFormat = options.PixelFormatHint;
|
||||
if (imageFormat == ImageFileFormat.Bmp && targetFormat == ImagePixelFormat.Unsupported &&
|
||||
PixelFormat == ImagePixelFormat.Gray8)
|
||||
{
|
||||
// Workaround for issue in some macOS versions with 8bit BMPs
|
||||
targetFormat = ImagePixelFormat.RGB24;
|
||||
}
|
||||
if (targetFormat != PixelFormat)
|
||||
{
|
||||
using var copy = (MacImage) this.CopyWithPixelFormat(targetFormat);
|
||||
return copy.Rep.RepresentationUsingTypeProperties(fileType, props);
|
||||
}
|
||||
return Rep.RepresentationUsingTypeProperties(fileType, props);
|
||||
using var helper = PixelFormatHelper.Create(this, targetFormat);
|
||||
return helper.Image.Rep.RepresentationUsingTypeProperties(fileType, props);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,16 +77,16 @@ public interface IMemoryImage : IImageStorage
|
||||
/// </summary>
|
||||
/// <param name="path">The path to save the image file to.</param>
|
||||
/// <param name="imageFormat">The file format to use.</param>
|
||||
/// <param name="quality">The quality parameter for JPEG compression, if applicable. -1 for default.</param>
|
||||
void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified, int quality = -1);
|
||||
/// <param name="options">Options for saving, e.g. JPEG quality.</param>
|
||||
void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified, ImageSaveOptions? options = null);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the image to the given stream. The file format must be specified.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to save the image to.</param>
|
||||
/// <param name="imageFormat">The file format to use.</param>
|
||||
/// <param name="quality">The quality parameter for JPEG compression, if applicable. -1 for default.</param>
|
||||
void Save(Stream stream, ImageFileFormat imageFormat, int quality = -1);
|
||||
/// <param name="options">Options for saving, e.g. JPEG quality.</param>
|
||||
void Save(Stream stream, ImageFileFormat imageFormat, ImageSaveOptions? options = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of the image so that one can be edited or disposed without affecting the other.
|
||||
|
@ -92,10 +92,10 @@ public static class ImageExtensions
|
||||
}
|
||||
|
||||
public static MemoryStream SaveToMemoryStream(this IMemoryImage image, ImageFileFormat imageFormat,
|
||||
int quality = -1)
|
||||
ImageSaveOptions? options = null)
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
image.Save(stream, imageFormat, quality);
|
||||
image.Save(stream, imageFormat, options);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return stream;
|
||||
}
|
||||
|
@ -5,6 +5,6 @@ public enum ImagePixelFormat
|
||||
Unsupported,
|
||||
BW1,
|
||||
Gray8,
|
||||
RGB24, // This is actually BGR in the binary representation
|
||||
RGB24,
|
||||
ARGB32
|
||||
}
|
18
NAPS2.Images/ImageSaveOptions.cs
Normal file
18
NAPS2.Images/ImageSaveOptions.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace NAPS2.Images;
|
||||
|
||||
public record ImageSaveOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The quality parameter for JPEG compression, if applicable. -1 for default.
|
||||
/// </summary>
|
||||
public int Quality { get; init; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The preferred pixel format that should be used for saving. If not specified, the image's LogicalPixelFormat
|
||||
/// will be preferred to minimize disk space used.
|
||||
/// <para/>
|
||||
/// This will not result in a loss of information, e.g. if you set this to BW1 but your image has color in it, it
|
||||
/// will have no effect. If you want to change the color information, use CopyWithPixelFormat before saving.
|
||||
/// </summary>
|
||||
public ImagePixelFormat PixelFormatHint { get; init; }
|
||||
}
|
@ -31,6 +31,15 @@
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>NAPS2.Lib.WinForms</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>NAPS2.Images.Gdi</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>NAPS2.Images.Gtk</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>NAPS2.Images.Mac</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\NAPS2.Setup\targets\CommonTargets.targets" />
|
||||
|
59
NAPS2.Images/PixelFormatHelper.cs
Normal file
59
NAPS2.Images/PixelFormatHelper.cs
Normal file
@ -0,0 +1,59 @@
|
||||
namespace NAPS2.Images;
|
||||
|
||||
// TODO: Use this for TIFF saving too, maybe
|
||||
internal static class PixelFormatHelper
|
||||
{
|
||||
public static PixelFormatHelper<T> Create<T>(T image,
|
||||
ImagePixelFormat targetFormat = ImagePixelFormat.Unsupported,
|
||||
ImagePixelFormat minFormat = ImagePixelFormat.Unsupported) where T : IMemoryImage
|
||||
{
|
||||
return new PixelFormatHelper<T>(image, targetFormat, minFormat);
|
||||
}
|
||||
}
|
||||
|
||||
internal class PixelFormatHelper<T> : IDisposable where T : IMemoryImage
|
||||
{
|
||||
public PixelFormatHelper(T image, ImagePixelFormat targetFormat, ImagePixelFormat minFormat)
|
||||
{
|
||||
// TODO: Maybe we can be aware of the target filetype, e.g. JPEG doesn't have 1bpp. Although the specifics
|
||||
// are going to be platform-dependent.
|
||||
if (targetFormat == ImagePixelFormat.Unsupported)
|
||||
{
|
||||
// If targetFormat is not specified, we'll use the logical format to minimize on-disk size.
|
||||
targetFormat = image.LogicalPixelFormat;
|
||||
}
|
||||
if (targetFormat < image.LogicalPixelFormat)
|
||||
{
|
||||
// We never want to lose color information.
|
||||
targetFormat = image.LogicalPixelFormat;
|
||||
}
|
||||
if (targetFormat < minFormat)
|
||||
{
|
||||
// GTK only supports RGB24/ARGB32 so it's pointless to target BW1/Gray8 as it will end up as RGB24 anyway.
|
||||
targetFormat = minFormat;
|
||||
}
|
||||
|
||||
if (targetFormat != image.PixelFormat)
|
||||
{
|
||||
Image = (T) image.CopyWithPixelFormat(targetFormat);
|
||||
IsCopy = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Image = image;
|
||||
IsCopy = false;
|
||||
}
|
||||
}
|
||||
|
||||
public T Image { get; }
|
||||
|
||||
public bool IsCopy { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsCopy)
|
||||
{
|
||||
Image.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -156,9 +156,9 @@ public class SaveImagesOperation : OperationBase
|
||||
private void DoSaveImage(ProcessedImage image, string path, ImageFileFormat format, ImageSettings imageSettings)
|
||||
{
|
||||
FileSystemHelper.EnsureParentDirExists(path);
|
||||
using var renderedImage = image.Render();
|
||||
if (format == ImageFileFormat.Tiff)
|
||||
{
|
||||
using var renderedImage = image.Render();
|
||||
_imageContext.TiffWriter.SaveTiff(new[] { renderedImage }, path,
|
||||
imageSettings.TiffCompression.ToTiffCompressionType(), CancelToken);
|
||||
}
|
||||
@ -167,8 +167,7 @@ public class SaveImagesOperation : OperationBase
|
||||
// Quality will be ignored when not needed
|
||||
// TODO: Scale quality differently for jpeg2000?
|
||||
var quality = imageSettings.JpegQuality.Clamp(0, 100);
|
||||
using var bitmap = image.Render();
|
||||
bitmap.Save(path, format, quality);
|
||||
renderedImage.Save(path, format, new ImageSaveOptions { Quality = quality });
|
||||
}
|
||||
}
|
||||
}
|
@ -169,6 +169,82 @@ public class LoadSaveTests : ContextualTests
|
||||
ImageAsserts.Similar(ImageResources.dog, images[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SavePngOptimizesBitDepth()
|
||||
{
|
||||
var image32Bpp = LoadImage(ImageResources.dog_bw).CopyWithPixelFormat(ImagePixelFormat.ARGB32);
|
||||
var image24Bpp = LoadImage(ImageResources.dog_bw).CopyWithPixelFormat(ImagePixelFormat.RGB24);
|
||||
var image8Bpp = LoadImage(ImageResources.dog_bw).CopyWithPixelFormat(ImagePixelFormat.Gray8);
|
||||
var image1Bpp = LoadImage(ImageResources.dog_bw).CopyWithPixelFormat(ImagePixelFormat.BW1);
|
||||
|
||||
var optimized32Bpp = GetSavedSize(image24Bpp, ImageFileFormat.Png);
|
||||
var optimized24Bpp = GetSavedSize(image24Bpp, ImageFileFormat.Png);
|
||||
var optimized8Bpp = GetSavedSize(image8Bpp, ImageFileFormat.Png);
|
||||
var optimized1Bpp = GetSavedSize(image1Bpp, ImageFileFormat.Png);
|
||||
|
||||
// All should be equal as since the logical pixel format is BW1, all should be converted to BW1 for saving.
|
||||
Assert.Equal(optimized24Bpp, optimized32Bpp);
|
||||
Assert.Equal(optimized24Bpp, optimized8Bpp);
|
||||
Assert.Equal(optimized24Bpp, optimized1Bpp);
|
||||
}
|
||||
|
||||
// Gtk does not support saving with 1bpp/8bpp
|
||||
[PlatformFact(exclude: PlatformFlags.Linux)]
|
||||
public void SavePngWithUnoptimizedBitDepth()
|
||||
{
|
||||
var image32Bpp = LoadImage(ImageResources.dog_bw).CopyWithPixelFormat(ImagePixelFormat.ARGB32);
|
||||
var image24Bpp = LoadImage(ImageResources.dog_bw).CopyWithPixelFormat(ImagePixelFormat.RGB24);
|
||||
var image8Bpp = LoadImage(ImageResources.dog_bw).CopyWithPixelFormat(ImagePixelFormat.Gray8);
|
||||
var image1Bpp = LoadImage(ImageResources.dog_bw).CopyWithPixelFormat(ImagePixelFormat.BW1);
|
||||
|
||||
// Specifying a PixelFormatHint equal to the real pixel format prevents optimized saving.
|
||||
|
||||
var optimized32Bpp = GetSavedSize(image32Bpp, ImageFileFormat.Png);
|
||||
var unoptimized32Bpp = GetSavedSize(image32Bpp, ImageFileFormat.Png,
|
||||
new ImageSaveOptions { PixelFormatHint = ImagePixelFormat.ARGB32 });
|
||||
|
||||
var optimized24Bpp = GetSavedSize(image24Bpp, ImageFileFormat.Png);
|
||||
var unoptimized24Bpp = GetSavedSize(image24Bpp, ImageFileFormat.Png,
|
||||
new ImageSaveOptions { PixelFormatHint = ImagePixelFormat.RGB24 });
|
||||
|
||||
var optimized8Bpp = GetSavedSize(image8Bpp, ImageFileFormat.Png);
|
||||
var unoptimized8Bpp = GetSavedSize(image8Bpp, ImageFileFormat.Png,
|
||||
new ImageSaveOptions { PixelFormatHint = ImagePixelFormat.Gray8 });
|
||||
|
||||
var optimized1Bpp = GetSavedSize(image1Bpp, ImageFileFormat.Png);
|
||||
var unoptimized1Bpp = GetSavedSize(image1Bpp, ImageFileFormat.Png,
|
||||
new ImageSaveOptions { PixelFormatHint = ImagePixelFormat.BW1 });
|
||||
|
||||
// All optimized values should be less than their unoptimized counterparts.
|
||||
Assert.True(optimized32Bpp < unoptimized32Bpp);
|
||||
Assert.True(optimized24Bpp < unoptimized24Bpp);
|
||||
Assert.True(optimized8Bpp < unoptimized8Bpp);
|
||||
Assert.Equal(optimized1Bpp, unoptimized1Bpp);
|
||||
|
||||
// Verify that 1bpp < 8bpp < 24bpp. 32bpp and 24bpp should be close but may vary so it isn't worth testing.
|
||||
Assert.True(unoptimized1Bpp < unoptimized8Bpp);
|
||||
Assert.True(unoptimized8Bpp < unoptimized24Bpp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PixelFormatHintDoesntLoseColor()
|
||||
{
|
||||
var original = LoadImage(ImageResources.dog);
|
||||
|
||||
var stream = new MemoryStream();
|
||||
original.Save(stream, ImageFileFormat.Png, new ImageSaveOptions { PixelFormatHint = ImagePixelFormat.BW1 });
|
||||
|
||||
var copy = ImageContext.Load(stream);
|
||||
ImageAsserts.Similar(ImageResources.dog, copy);
|
||||
}
|
||||
|
||||
private int GetSavedSize(IMemoryImage image, ImageFileFormat fileFormat, ImageSaveOptions options = null)
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
image.Save(stream, fileFormat, options);
|
||||
return (int) stream.Length;
|
||||
}
|
||||
|
||||
private static byte[] GetResource(string resource) =>
|
||||
(byte[]) ImageResources.ResourceManager.GetObject(resource, CultureInfo.InvariantCulture);
|
||||
|
||||
|
@ -33,8 +33,8 @@ public class MemoryImageTests : ContextualTests
|
||||
var highQualityPath = Path.Combine(FolderPath, "highq.jpg");
|
||||
var lowQualityPath = Path.Combine(FolderPath, "lowq.jpg");
|
||||
|
||||
image.Save(highQualityPath, ImageFileFormat.Jpeg, 75);
|
||||
image.Save(lowQualityPath, ImageFileFormat.Jpeg, 25);
|
||||
image.Save(highQualityPath, ImageFileFormat.Jpeg, new ImageSaveOptions { Quality = 75 });
|
||||
image.Save(lowQualityPath, ImageFileFormat.Jpeg, new ImageSaveOptions { Quality = 25 });
|
||||
|
||||
var highQuality = TestImageContextFactory.Get().Load(highQualityPath);
|
||||
var lowQuality = TestImageContextFactory.Get().Load(lowQualityPath);
|
||||
@ -66,8 +66,8 @@ public class MemoryImageTests : ContextualTests
|
||||
var highQualityStream = new MemoryStream();
|
||||
var lowQualityStream = new MemoryStream();
|
||||
|
||||
image.Save(highQualityStream, ImageFileFormat.Jpeg, 75);
|
||||
image.Save(lowQualityStream, ImageFileFormat.Jpeg, 25);
|
||||
image.Save(highQualityStream, ImageFileFormat.Jpeg, new ImageSaveOptions { Quality = 75 });
|
||||
image.Save(lowQualityStream, ImageFileFormat.Jpeg, new ImageSaveOptions { Quality = 25 });
|
||||
|
||||
var highQuality = TestImageContextFactory.Get().Load(highQualityStream);
|
||||
var lowQuality = TestImageContextFactory.Get().Load(lowQualityStream);
|
||||
|
@ -31,11 +31,11 @@ public class ImageExportHelper
|
||||
if (exportFormat.FileFormat == ImageFileFormat.Jpeg)
|
||||
{
|
||||
imageFileFormat = ImageFileFormat.Jpeg;
|
||||
return image.SaveToMemoryStream(ImageFileFormat.Jpeg, quality);
|
||||
return image.SaveToMemoryStream(ImageFileFormat.Jpeg, new ImageSaveOptions { Quality = quality });
|
||||
}
|
||||
// Save as PNG/JPEG depending on which is smaller
|
||||
var pngEncoded = image.SaveToMemoryStream(ImageFileFormat.Png);
|
||||
var jpegEncoded = image.SaveToMemoryStream(ImageFileFormat.Jpeg, quality);
|
||||
var jpegEncoded = image.SaveToMemoryStream(ImageFileFormat.Jpeg, new ImageSaveOptions { Quality = quality });
|
||||
if (pngEncoded.Length <= jpegEncoded.Length)
|
||||
{
|
||||
// Probably a black and white image (e.g. from native WIA, where bitDepth is unknown), which PNG compresses well vs. JPEG
|
||||
|
@ -511,16 +511,8 @@ public class PdfExporter : IPdfExporter
|
||||
|
||||
public void SaveAsJpeg(MemoryStream ms)
|
||||
{
|
||||
if (_image.PixelFormat == ImagePixelFormat.Gray8)
|
||||
{
|
||||
// PDFs require RGB channels so we need to make sure we're exporting that
|
||||
using var copy = _image.CopyWithPixelFormat(ImagePixelFormat.RGB24);
|
||||
copy.Save(ms, ImageFileFormat.Jpeg);
|
||||
}
|
||||
else
|
||||
{
|
||||
_image.Save(ms, ImageFileFormat.Jpeg);
|
||||
}
|
||||
// PDFs require RGB channels so we need to make sure we're exporting that.
|
||||
_image.Save(ms, ImageFileFormat.Jpeg, new ImageSaveOptions { PixelFormatHint = ImagePixelFormat.RGB24 });
|
||||
}
|
||||
|
||||
public void SaveAsPdfBitmap(MemoryStream ms)
|
||||
|
Loading…
Reference in New Issue
Block a user