mirror of
https://github.com/cyanfish/naps2.git
synced 2024-11-11 02:45:19 +03:00
Storage wip (not compiling yet)
This commit is contained in:
parent
744bc6a5fd
commit
6e30ec6cc9
@ -20,7 +20,6 @@ namespace NAPS2.DI.Modules
|
||||
Bind<IOverwritePrompt>().To<ConsoleOverwritePrompt>();
|
||||
Bind<IOperationProgress>().To<ConsoleOperationProgress>();
|
||||
Bind<IComponentInstallPrompt>().To<ConsoleComponentInstallPrompt>();
|
||||
Bind<ThumbnailRenderer>().To<NullThumbnailRenderer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NAPS2.Config;
|
||||
using NAPS2.ImportExport.Pdf;
|
||||
using NAPS2.Logging;
|
||||
using NAPS2.Ocr;
|
||||
using NAPS2.Platform;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NLog;
|
||||
|
||||
namespace NAPS2.DI
|
||||
@ -34,6 +36,11 @@ namespace NAPS2.DI
|
||||
|
||||
GhostscriptManager.BasePath = basePath;
|
||||
OcrManager.Default = new OcrManager(basePath);
|
||||
|
||||
var recoveryFolderPath = Path.Combine(Paths.Recovery, Path.GetRandomFileName());
|
||||
var rsm = new RecoveryStorageManager(recoveryFolderPath);
|
||||
FileStorageManager.Default = rsm;
|
||||
StorageManager.ImageMetadataFactory = rsm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,17 +7,18 @@ using NAPS2.Lang.Resources;
|
||||
using NAPS2.Logging;
|
||||
using NAPS2.Operation;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Util;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
|
||||
namespace NAPS2.ImportExport
|
||||
{
|
||||
public class DirectImportOperation : OperationBase
|
||||
{
|
||||
private readonly ThumbnailRenderer thumbnailRenderer;
|
||||
private readonly ScannedImageRenderer scannedImageRenderer;
|
||||
|
||||
public DirectImportOperation(ThumbnailRenderer thumbnailRenderer)
|
||||
public DirectImportOperation(ScannedImageRenderer scannedImageRenderer)
|
||||
{
|
||||
this.thumbnailRenderer = thumbnailRenderer;
|
||||
this.scannedImageRenderer = scannedImageRenderer;
|
||||
|
||||
AllowCancel = true;
|
||||
AllowBackground = true;
|
||||
@ -40,16 +41,16 @@ namespace NAPS2.ImportExport
|
||||
try
|
||||
{
|
||||
ScannedImage img;
|
||||
using (var bitmap = new Bitmap(Path.Combine(data.RecoveryFolder, ir.FileName)))
|
||||
using (var storage = StorageManager.ConvertToMemory(new FileStorage(Path.Combine(data.RecoveryFolder, ir.FileName)), new StorageConvertParams()))
|
||||
{
|
||||
img = new ScannedImage(bitmap, ir.BitDepth, ir.HighQuality, -1);
|
||||
img = new ScannedImage(storage, ir.BitDepth, ir.HighQuality, -1);
|
||||
}
|
||||
foreach (var transform in ir.TransformList)
|
||||
{
|
||||
img.AddTransform(transform);
|
||||
}
|
||||
// TODO: Don't bother, here, in recovery, etc.
|
||||
img.SetThumbnail(await thumbnailRenderer.RenderThumbnail(img));
|
||||
img.SetThumbnail(StorageManager.PerformTransform(await scannedImageRenderer.Render(img), new ThumbnailTransform()));
|
||||
imageCallback(img);
|
||||
|
||||
Status.CurrentProgress++;
|
||||
|
@ -8,6 +8,8 @@ using System.Threading.Tasks;
|
||||
using NAPS2.Logging;
|
||||
using NAPS2.Scan;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.ImportExport.Images
|
||||
@ -34,10 +36,11 @@ namespace NAPS2.ImportExport.Images
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap toImport;
|
||||
IEnumerable<IMemoryStorage> toImport;
|
||||
int frameCount;
|
||||
try
|
||||
{
|
||||
toImport = new Bitmap(filePath);
|
||||
toImport = StorageManager.MemoryStorageFactory.DecodeMultiple(filePath, out frameCount);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -46,12 +49,11 @@ namespace NAPS2.ImportExport.Images
|
||||
throw;
|
||||
}
|
||||
|
||||
using (toImport)
|
||||
foreach (var frame in toImport)
|
||||
{
|
||||
using (frame)
|
||||
{
|
||||
int frameCount = toImport.GetFrameCount(FrameDimension.Page);
|
||||
int i = 0;
|
||||
foreach (var frameIndex in importParams.Slice.Indices(frameCount))
|
||||
{
|
||||
progressCallback(i++, frameCount);
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
{
|
||||
@ -59,15 +61,15 @@ namespace NAPS2.ImportExport.Images
|
||||
return;
|
||||
}
|
||||
|
||||
toImport.SelectActiveFrame(FrameDimension.Page, frameIndex);
|
||||
var image = new ScannedImage(toImport, ScanBitDepth.C24Bit, IsLossless(toImport.RawFormat), -1);
|
||||
var image = new ScannedImage(frame, ScanBitDepth.C24Bit, frame.IsOriginalLossless, -1);
|
||||
if (!importParams.NoThumbnails)
|
||||
{
|
||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(toImport));
|
||||
image.SetThumbnail(StorageManager.PerformTransform(frame, new ThumbnailTransform()));
|
||||
}
|
||||
|
||||
if (importParams.DetectPatchCodes)
|
||||
{
|
||||
image.PatchCode = PatchCodeDetector.Detect(toImport);
|
||||
image.PatchCode = PatchCodeDetector.Detect(frame);
|
||||
}
|
||||
|
||||
source.Put(image);
|
||||
@ -75,6 +77,7 @@ namespace NAPS2.ImportExport.Images
|
||||
|
||||
progressCallback(frameCount, frameCount);
|
||||
}
|
||||
|
||||
source.Done();
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -84,10 +87,5 @@ namespace NAPS2.ImportExport.Images
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
return source;
|
||||
}
|
||||
|
||||
private bool IsLossless(ImageFormat format)
|
||||
{
|
||||
return Equals(format, ImageFormat.Bmp) || Equals(format, ImageFormat.Png);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ using NAPS2.Lang.Resources;
|
||||
using NAPS2.Logging;
|
||||
using NAPS2.Operation;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.ImportExport.Images
|
||||
@ -173,14 +174,15 @@ namespace NAPS2.ImportExport.Images
|
||||
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 (Bitmap bitmap = await scannedImageRenderer.Render(snapshot))
|
||||
// TODO: Something more generic
|
||||
using (Bitmap bitmap = ((GdiStorage) await scannedImageRenderer.Render(snapshot)).Bitmap)
|
||||
{
|
||||
bitmap.Save(path, encoder, encoderParams);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Bitmap bitmap = await scannedImageRenderer.Render(snapshot))
|
||||
using (Bitmap bitmap = ((GdiStorage)await scannedImageRenderer.Render(snapshot)).Bitmap)
|
||||
{
|
||||
bitmap.Save(path, format);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.ImportExport.Images
|
||||
@ -39,7 +40,8 @@ namespace NAPS2.ImportExport.Images
|
||||
{
|
||||
var iparams = new EncoderParameters(1);
|
||||
Encoder iparam = Encoder.Compression;
|
||||
using (var bitmap = await scannedImageRenderer.Render(snapshots[0]))
|
||||
// TODO: More generic (?)
|
||||
using (var bitmap = ((GdiStorage)await scannedImageRenderer.Render(snapshots[0])).Bitmap)
|
||||
{
|
||||
ValidateBitmap(bitmap);
|
||||
var iparamPara = new EncoderParameter(iparam, (long)GetEncoderValue(compression, bitmap));
|
||||
@ -54,7 +56,7 @@ namespace NAPS2.ImportExport.Images
|
||||
var compressionEncoder = Encoder.Compression;
|
||||
|
||||
File.Delete(location);
|
||||
using (var bitmap0 = await scannedImageRenderer.Render(snapshots[0]))
|
||||
using (var bitmap0 = ((GdiStorage)await scannedImageRenderer.Render(snapshots[0])).Bitmap)
|
||||
{
|
||||
ValidateBitmap(bitmap0);
|
||||
encoderParams.Param[0] = new EncoderParameter(compressionEncoder, (long)GetEncoderValue(compression, bitmap0));
|
||||
@ -74,7 +76,7 @@ namespace NAPS2.ImportExport.Images
|
||||
return false;
|
||||
}
|
||||
|
||||
using (var bitmap = await scannedImageRenderer.Render(snapshots[i]))
|
||||
using (var bitmap = ((GdiStorage)await scannedImageRenderer.Render(snapshots[i])).Bitmap)
|
||||
{
|
||||
ValidateBitmap(bitmap);
|
||||
encoderParams.Param[0] = new EncoderParameter(compressionEncoder, (long)GetEncoderValue(compression, bitmap));
|
||||
|
@ -13,6 +13,8 @@ using NAPS2.Lang.Resources;
|
||||
using NAPS2.Logging;
|
||||
using NAPS2.Scan;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
using PdfSharp.Pdf;
|
||||
using PdfSharp.Pdf.Advanced;
|
||||
@ -179,19 +181,22 @@ namespace NAPS2.ImportExport.Pdf
|
||||
|
||||
private async Task<ScannedImage> ExportRawPdfPage(PdfPage page, ImportParams importParams)
|
||||
{
|
||||
string pdfPath = Path.Combine(Paths.Temp, Path.GetRandomFileName());
|
||||
string pdfPath = FileStorageManager.Default.NextFilePath();
|
||||
var document = new PdfDocument();
|
||||
document.Pages.Add(page);
|
||||
document.Save(pdfPath);
|
||||
|
||||
var image = ScannedImage.FromSinglePagePdf(pdfPath, false);
|
||||
// TODO: It would make sense to have in-memory PDFs be an option.
|
||||
// TODO: Really, ConvertToBacking should convert PdfStorage -> PdfFileStorage.
|
||||
// TODO: Then we wouldn't need a static FileStorageManager.
|
||||
var image = new ScannedImage(new PdfFileStorage(pdfPath));
|
||||
if (!importParams.NoThumbnails || importParams.DetectPatchCodes)
|
||||
{
|
||||
using (var bitmap = await scannedImageRenderer.Render(image))
|
||||
{
|
||||
if (!importParams.NoThumbnails)
|
||||
{
|
||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
||||
image.SetThumbnail(StorageManager.PerformTransform(bitmap, new ThumbnailTransform()));
|
||||
}
|
||||
if (importParams.DetectPatchCodes)
|
||||
{
|
||||
@ -207,17 +212,17 @@ namespace NAPS2.ImportExport.Pdf
|
||||
// Fortunately JPEG has native support in PDF and exporting an image is just writing the stream to a file.
|
||||
using (var memoryStream = new MemoryStream(imageBytes))
|
||||
{
|
||||
using (var bitmap = new Bitmap(memoryStream))
|
||||
using (var storage = StorageManager.MemoryStorageFactory.Decode(memoryStream, ".jpg"))
|
||||
{
|
||||
bitmap.SafeSetResolution(bitmap.Width / (float)page.Width.Inch, bitmap.Height / (float)page.Height.Inch);
|
||||
var image = new ScannedImage(bitmap, ScanBitDepth.C24Bit, false, -1);
|
||||
storage.SetResolution(storage.Width / (float)page.Width.Inch, storage.Height / (float)page.Height.Inch);
|
||||
var image = new ScannedImage(storage, ScanBitDepth.C24Bit, false, -1);
|
||||
if (!importParams.NoThumbnails)
|
||||
{
|
||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
||||
image.SetThumbnail(StorageManager.PerformTransform(storage, new ThumbnailTransform()));
|
||||
}
|
||||
if (importParams.DetectPatchCodes)
|
||||
{
|
||||
image.PatchCode = PatchCodeDetector.Detect(bitmap);
|
||||
image.PatchCode = PatchCodeDetector.Detect(storage);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
@ -232,51 +237,51 @@ namespace NAPS2.ImportExport.Pdf
|
||||
|
||||
var buffer = imageObject.Stream.UnfilteredValue;
|
||||
|
||||
Bitmap bitmap;
|
||||
IMemoryStorage storage;
|
||||
ScanBitDepth bitDepth;
|
||||
switch (bitsPerComponent)
|
||||
{
|
||||
case 8:
|
||||
bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
|
||||
storage = StorageManager.MemoryStorageFactory.FromDimensions(width, height, StoragePixelFormat.RGB24);
|
||||
bitDepth = ScanBitDepth.C24Bit;
|
||||
RgbToBitmapUnmanaged(height, width, bitmap, buffer);
|
||||
RgbToBitmapUnmanaged(storage, buffer);
|
||||
break;
|
||||
case 1:
|
||||
bitmap = new Bitmap(width, height, PixelFormat.Format1bppIndexed);
|
||||
storage = StorageManager.MemoryStorageFactory.FromDimensions(width, height, StoragePixelFormat.BW1);
|
||||
bitDepth = ScanBitDepth.BlackWhite;
|
||||
BlackAndWhiteToBitmapUnmanaged(height, width, bitmap, buffer);
|
||||
BlackAndWhiteToBitmapUnmanaged(storage, buffer);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("Unsupported image encoding (expected 24 bpp or 1bpp)");
|
||||
}
|
||||
|
||||
using (bitmap)
|
||||
using (storage)
|
||||
{
|
||||
bitmap.SafeSetResolution(bitmap.Width / (float)page.Width.Inch, bitmap.Height / (float)page.Height.Inch);
|
||||
var image = new ScannedImage(bitmap, bitDepth, true, -1);
|
||||
storage.SetResolution(storage.Width / (float)page.Width.Inch, storage.Height / (float)page.Height.Inch);
|
||||
var image = new ScannedImage(storage, bitDepth, true, -1);
|
||||
if (!importParams.NoThumbnails)
|
||||
{
|
||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
||||
image.SetThumbnail(StorageManager.PerformTransform(storage, new ThumbnailTransform()));
|
||||
}
|
||||
if (importParams.DetectPatchCodes)
|
||||
{
|
||||
image.PatchCode = PatchCodeDetector.Detect(bitmap);
|
||||
image.PatchCode = PatchCodeDetector.Detect(storage);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RgbToBitmapUnmanaged(int height, int width, Bitmap bitmap, byte[] rgbBuffer)
|
||||
private static void RgbToBitmapUnmanaged(IMemoryStorage storage, byte[] rgbBuffer)
|
||||
{
|
||||
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
|
||||
var data = storage.Lock(out var scan0, out var stride);
|
||||
try
|
||||
{
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int y = 0; y < storage.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
for (int x = 0; x < storage.Width; x++)
|
||||
{
|
||||
IntPtr pixelData = data.Scan0 + y * data.Stride + x * 3;
|
||||
int bufferIndex = (y * width + x) * 3;
|
||||
IntPtr pixelData = scan0 + y * stride + x * 3;
|
||||
int bufferIndex = (y * storage.Width + x) * 3;
|
||||
Marshal.WriteByte(pixelData, rgbBuffer[bufferIndex + 2]);
|
||||
Marshal.WriteByte(pixelData + 1, rgbBuffer[bufferIndex + 1]);
|
||||
Marshal.WriteByte(pixelData + 2, rgbBuffer[bufferIndex]);
|
||||
@ -285,28 +290,28 @@ namespace NAPS2.ImportExport.Pdf
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap.UnlockBits(data);
|
||||
storage.Unlock(data);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BlackAndWhiteToBitmapUnmanaged(int height, int width, Bitmap bitmap, byte[] bwBuffer)
|
||||
private static void BlackAndWhiteToBitmapUnmanaged(IMemoryStorage storage, byte[] bwBuffer)
|
||||
{
|
||||
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
|
||||
var data = storage.Lock(out var scan0, out var stride);
|
||||
try
|
||||
{
|
||||
int bytesPerRow = (width - 1) / 8 + 1;
|
||||
for (int y = 0; y < height; y++)
|
||||
int bytesPerRow = (storage.Width - 1) / 8 + 1;
|
||||
for (int y = 0; y < storage.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bytesPerRow; x++)
|
||||
{
|
||||
IntPtr pixelData = data.Scan0 + y * data.Stride + x;
|
||||
IntPtr pixelData = scan0 + y * stride + x;
|
||||
Marshal.WriteByte(pixelData, bwBuffer[y * bytesPerRow + x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap.UnlockBits(data);
|
||||
storage.Unlock(data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,18 +362,18 @@ namespace NAPS2.ImportExport.Pdf
|
||||
Write(stream, TiffTrailer);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using (Bitmap bitmap = (Bitmap)Image.FromStream(stream))
|
||||
using (var storage = StorageManager.MemoryStorageFactory.Decode(stream, ".tiff"))
|
||||
{
|
||||
bitmap.SafeSetResolution(bitmap.Width / (float)page.Width.Inch, bitmap.Height / (float)page.Height.Inch);
|
||||
storage.SetResolution(storage.Width / (float)page.Width.Inch, storage.Height / (float)page.Height.Inch);
|
||||
|
||||
var image = new ScannedImage(bitmap, ScanBitDepth.BlackWhite, true, -1);
|
||||
var image = new ScannedImage(storage, ScanBitDepth.BlackWhite, true, -1);
|
||||
if (!importParams.NoThumbnails)
|
||||
{
|
||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
||||
image.SetThumbnail(StorageManager.PerformTransform(storage, new ThumbnailTransform()));
|
||||
}
|
||||
if (importParams.DetectPatchCodes)
|
||||
{
|
||||
image.PatchCode = PatchCodeDetector.Detect(bitmap);
|
||||
image.PatchCode = PatchCodeDetector.Detect(storage);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using System.Windows.Forms;
|
||||
using NAPS2.Lang.Resources;
|
||||
using NAPS2.Logging;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
|
||||
@ -88,7 +89,7 @@ namespace NAPS2.ImportExport
|
||||
if (Math.Sign(image.Width - image.Height) != Math.Sign(pb.Width - pb.Height))
|
||||
{
|
||||
// Flip portrait/landscape to match output
|
||||
image = new RotationTransform(90).Perform(image);
|
||||
image = StorageManager.PerformTransform(image, new RotationTransform(90));
|
||||
}
|
||||
|
||||
// Fit the image into the output rect while maintaining its aspect ratio
|
||||
@ -96,7 +97,7 @@ namespace NAPS2.ImportExport
|
||||
? new Rectangle(pb.Left, pb.Top, image.Width * pb.Height / image.Height, pb.Height)
|
||||
: new Rectangle(pb.Left, pb.Top, pb.Width, image.Height * pb.Width / image.Width);
|
||||
|
||||
e.Graphics.DrawImage(image, rect);
|
||||
e.Graphics.DrawImage(StorageManager.Convert<GdiStorage>(image).Bitmap, rect);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -191,6 +191,11 @@
|
||||
<Compile Include="Platform\DefaultRuntimeCompat.cs" />
|
||||
<Compile Include="Scan\Exceptions\DriverNotSupportedException.cs" />
|
||||
<Compile Include="Scan\Images\ScannedImageSource.cs" />
|
||||
<Compile Include="Scan\Images\Storage\GdiTransformer.cs" />
|
||||
<Compile Include="Scan\Images\Storage\IFileStorage.cs" />
|
||||
<Compile Include="Scan\Images\Storage\IImageMetadata.cs" />
|
||||
<Compile Include="Scan\Images\Storage\IImageMetadataFactory.cs" />
|
||||
<Compile Include="Scan\Images\Storage\ITransformer.cs" />
|
||||
<Compile Include="Scan\Images\Storage\PdfFileStorage.cs" />
|
||||
<Compile Include="Scan\Images\Storage\FileStorageManager.cs" />
|
||||
<Compile Include="Scan\Images\Storage\GdiFileConverter.cs" />
|
||||
@ -201,10 +206,15 @@
|
||||
<Compile Include="Scan\Images\Storage\IStorage.cs" />
|
||||
<Compile Include="Scan\Images\Storage\IStorageConverter.cs" />
|
||||
<Compile Include="Scan\Images\Storage\IMemoryStorageFactory.cs" />
|
||||
<Compile Include="Scan\Images\Storage\RecoverableImageMetadata.cs" />
|
||||
<Compile Include="Scan\Images\Storage\RecoveryStorageManager.cs" />
|
||||
<Compile Include="Scan\Images\Storage\StorageConvertParams.cs" />
|
||||
<Compile Include="Scan\Images\Storage\StorageManager.cs" />
|
||||
<Compile Include="Scan\Images\Storage\StoragePixelFormat.cs" />
|
||||
<Compile Include="Scan\Images\Storage\StubImageMetadata.cs" />
|
||||
<Compile Include="Scan\Images\Storage\StubImageMetadataFactory.cs" />
|
||||
<Compile Include="Scan\Images\Transforms\ThumbnailTransform.cs" />
|
||||
<Compile Include="Scan\Images\Transforms\ScaleTransform.cs" />
|
||||
<Compile Include="Scan\KeyValueScanOptions.cs" />
|
||||
<Compile Include="Scan\Exceptions\SaneNotAvailableException.cs" />
|
||||
<Compile Include="Scan\Sane\SaneOption.cs" />
|
||||
@ -345,7 +355,6 @@
|
||||
<Compile Include="Scan\Batch\BatchSettings.cs" />
|
||||
<Compile Include="Scan\Exceptions\NoDuplexSupportException.cs" />
|
||||
<Compile Include="Scan\Images\IBlankDetector.cs" />
|
||||
<Compile Include="Scan\Images\NullThumbnailRenderer.cs" />
|
||||
<Compile Include="Scan\Images\ThresholdBlankDetector.cs" />
|
||||
<Compile Include="Scan\Images\Transforms\BlackWhiteTransform.cs" />
|
||||
<Compile Include="Scan\Images\Transforms\ColorHelper.cs" />
|
||||
@ -470,7 +479,6 @@
|
||||
<Compile Include="Scan\Images\ScannedImageHelper.cs" />
|
||||
<Compile Include="Scan\Images\ScannedImageList.cs" />
|
||||
<Compile Include="Scan\Images\ThumbnailRenderer.cs" />
|
||||
<Compile Include="Scan\Images\ImageScaleHelper.cs" />
|
||||
<Compile Include="Scan\IScanDriver.cs" />
|
||||
<Compile Include="Scan\IScanPerformer.cs" />
|
||||
<Compile Include="Scan\LocalizedDescriptionAttribute.cs" />
|
||||
|
@ -1,14 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
|
||||
namespace NAPS2.Scan.Images
|
||||
{
|
||||
public interface IBlankDetector
|
||||
{
|
||||
bool IsBlank(Bitmap bitmap, int whiteThresholdNorm, int coverageThresholdNorm);
|
||||
bool IsBlank(IMemoryStorage bitmap, int whiteThresholdNorm, int coverageThresholdNorm);
|
||||
|
||||
bool ExcludePage(Bitmap bitmap, ScanProfile scanProfile);
|
||||
bool ExcludePage(IMemoryStorage bitmap, ScanProfile scanProfile);
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.Scan.Images
|
||||
{
|
||||
internal static class ImageScaleHelper
|
||||
{
|
||||
public static Bitmap ScaleImage(Image original, double scaleFactor)
|
||||
{
|
||||
double realWidth = original.Width / scaleFactor;
|
||||
double realHeight = original.Height / scaleFactor;
|
||||
|
||||
double horizontalRes = original.HorizontalResolution / scaleFactor;
|
||||
double verticalRes = original.VerticalResolution / scaleFactor;
|
||||
|
||||
var result = new Bitmap((int)realWidth, (int)realHeight, PixelFormat.Format24bppRgb);
|
||||
using (Graphics g = Graphics.FromImage(result))
|
||||
{
|
||||
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
g.DrawImage(original, 0, 0, (int)realWidth, (int)realHeight);
|
||||
result.SafeSetResolution((float)horizontalRes, (float)verticalRes);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NAPS2.Config;
|
||||
|
||||
namespace NAPS2.Scan.Images
|
||||
{
|
||||
public class NullThumbnailRenderer : ThumbnailRenderer
|
||||
{
|
||||
public NullThumbnailRenderer(ScannedImageRenderer scannedImageRenderer)
|
||||
: base(scannedImageRenderer)
|
||||
{
|
||||
}
|
||||
|
||||
public override Bitmap RenderThumbnail(Bitmap b, int size) => null;
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using NAPS2.Recovery;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
|
||||
@ -16,71 +17,86 @@ namespace NAPS2.Scan.Images
|
||||
{
|
||||
public class ScannedImage : IDisposable
|
||||
{
|
||||
// Store the base image and metadata on disk using a separate class to manage lifetime
|
||||
// If NAPS2 crashes, the image data can be recovered by the next instance of NAPS2 to start
|
||||
private readonly RecoveryImage recoveryImage;
|
||||
|
||||
// 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<Transform> transformList;
|
||||
|
||||
private Bitmap thumbnail;
|
||||
private IMemoryStorage thumbnail;
|
||||
private int thumbnailState;
|
||||
private int transformState;
|
||||
|
||||
private bool disposed;
|
||||
private int snapshotCount;
|
||||
|
||||
public static ScannedImage FromSinglePagePdf(string pdfPath, bool copy)
|
||||
//public static ScannedImage FromSinglePagePdf(string pdfPath, bool copy)
|
||||
//{
|
||||
// return new ScannedImage(pdfPath, copy);
|
||||
//}
|
||||
|
||||
//public ScannedImage(Bitmap img, ScanBitDepth bitDepth, bool highQuality, int quality)
|
||||
//{
|
||||
// string tempFilePath = ScannedImageHelper.SaveSmallestBitmap(img, bitDepth, highQuality, quality, out ImageFormat fileFormat);
|
||||
|
||||
// transformList = new List<Transform>();
|
||||
// recoveryImage = RecoveryImage.CreateNew(fileFormat, bitDepth, highQuality, transformList);
|
||||
|
||||
// File.Move(tempFilePath, recoveryImage.FilePath);
|
||||
|
||||
// recoveryImage.Save();
|
||||
//}
|
||||
|
||||
//public ScannedImage(RecoveryIndexImage recoveryIndexImage)
|
||||
//{
|
||||
// recoveryImage = RecoveryImage.LoadExisting(recoveryIndexImage);
|
||||
// transformList = recoveryImage.IndexImage.TransformList;
|
||||
//}
|
||||
|
||||
//private ScannedImage(string pdfPath, bool copy)
|
||||
//{
|
||||
// transformList = new List<Transform>();
|
||||
// recoveryImage = RecoveryImage.CreateNew(null, ScanBitDepth.C24Bit, false, transformList);
|
||||
|
||||
// if (copy)
|
||||
// {
|
||||
// File.Copy(pdfPath, recoveryImage.FilePath);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// File.Move(pdfPath, recoveryImage.FilePath);
|
||||
// }
|
||||
|
||||
// recoveryImage.Save();
|
||||
//}
|
||||
|
||||
public ScannedImage(IStorage storage) : this(storage, new StorageConvertParams())
|
||||
{
|
||||
return new ScannedImage(pdfPath, copy);
|
||||
}
|
||||
|
||||
public ScannedImage(Bitmap img, ScanBitDepth bitDepth, bool highQuality, int quality)
|
||||
public ScannedImage(IStorage storage, StorageConvertParams convertParams)
|
||||
{
|
||||
string tempFilePath = ScannedImageHelper.SaveSmallestBitmap(img, bitDepth, highQuality, quality, out ImageFormat fileFormat);
|
||||
|
||||
transformList = new List<Transform>();
|
||||
recoveryImage = RecoveryImage.CreateNew(fileFormat, bitDepth, highQuality, transformList);
|
||||
|
||||
File.Move(tempFilePath, recoveryImage.FilePath);
|
||||
|
||||
recoveryImage.Save();
|
||||
BackingStorage = StorageManager.ConvertToBacking(storage, convertParams);
|
||||
Metadata = StorageManager.ImageMetadataFactory.CreateMetadata(BackingStorage);
|
||||
Metadata.Commit();
|
||||
}
|
||||
|
||||
public ScannedImage(RecoveryIndexImage recoveryIndexImage)
|
||||
public ScannedImage(IStorage storage, IImageMetadata metadata, StorageConvertParams convertParams)
|
||||
{
|
||||
recoveryImage = RecoveryImage.LoadExisting(recoveryIndexImage);
|
||||
transformList = recoveryImage.IndexImage.TransformList;
|
||||
BackingStorage = StorageManager.ConvertToBacking(storage, convertParams);
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
private ScannedImage(string pdfPath, bool copy)
|
||||
public ScannedImage(IStorage storage, ScanBitDepth bitDepth, bool highQuality, int quality)
|
||||
{
|
||||
transformList = new List<Transform>();
|
||||
recoveryImage = RecoveryImage.CreateNew(null, ScanBitDepth.C24Bit, false, transformList);
|
||||
|
||||
if (copy)
|
||||
{
|
||||
File.Copy(pdfPath, recoveryImage.FilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Move(pdfPath, recoveryImage.FilePath);
|
||||
BackingStorage = StorageManager.ConvertToBacking(storage, new StorageConvertParams { Lossless = highQuality, LossyQuality = quality });
|
||||
Metadata = StorageManager.ImageMetadataFactory.CreateMetadata(BackingStorage);
|
||||
// TODO: Is this stuff really needed in metadata?
|
||||
Metadata.BitDepth = bitDepth;
|
||||
Metadata.Lossless = highQuality;
|
||||
Metadata.Commit();
|
||||
}
|
||||
|
||||
recoveryImage.Save();
|
||||
}
|
||||
public IStorage BackingStorage { get; }
|
||||
|
||||
public IImageMetadata Metadata { get; }
|
||||
|
||||
public PatchCode PatchCode { get; set; }
|
||||
|
||||
public ImageFormat FileFormat => recoveryImage.FileFormat;
|
||||
|
||||
public RecoveryIndexImage RecoveryIndexImage => recoveryImage.IndexImage;
|
||||
|
||||
public string RecoveryFilePath => recoveryImage.FilePath;
|
||||
|
||||
public long Size => new FileInfo(recoveryImage.FilePath).Length;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (this)
|
||||
@ -90,7 +106,7 @@ namespace NAPS2.Scan.Images
|
||||
if (snapshotCount != 0) return;
|
||||
|
||||
// Delete the image data on disk
|
||||
recoveryImage?.Dispose();
|
||||
BackingStorage?.Dispose();
|
||||
if (thumbnail != null)
|
||||
{
|
||||
thumbnail.Dispose();
|
||||
@ -106,13 +122,13 @@ namespace NAPS2.Scan.Images
|
||||
lock (this)
|
||||
{
|
||||
// Also updates the recovery index since they reference the same list
|
||||
if (!Transform.AddOrSimplify(transformList, transform))
|
||||
if (!Transform.AddOrSimplify(Metadata.TransformList, transform))
|
||||
{
|
||||
return;
|
||||
}
|
||||
transformState++;
|
||||
}
|
||||
recoveryImage.Save();
|
||||
Metadata.Commit();
|
||||
ThumbnailInvalidated?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
@ -120,26 +136,26 @@ namespace NAPS2.Scan.Images
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (transformList.Count == 0)
|
||||
if (Metadata.TransformList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
transformList.Clear();
|
||||
Metadata.TransformList.Clear();
|
||||
transformState++;
|
||||
}
|
||||
recoveryImage.Save();
|
||||
Metadata.Commit();
|
||||
ThumbnailInvalidated?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
public Bitmap GetThumbnail()
|
||||
public IMemoryStorage GetThumbnail()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
return (Bitmap) thumbnail?.Clone();
|
||||
return thumbnail?.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetThumbnail(Bitmap bitmap, int? state = null)
|
||||
public void SetThumbnail(IMemoryStorage bitmap, int? state = null)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
@ -160,7 +176,8 @@ namespace NAPS2.Scan.Images
|
||||
|
||||
public void MovedTo(int index)
|
||||
{
|
||||
recoveryImage.Move(index);
|
||||
Metadata.Index = index;
|
||||
Metadata.Commit();
|
||||
}
|
||||
|
||||
public Snapshot Preserve() => new Snapshot(this);
|
||||
@ -181,7 +198,7 @@ namespace NAPS2.Scan.Images
|
||||
}
|
||||
source.snapshotCount++;
|
||||
Source = source;
|
||||
TransformList = source.transformList.ToList();
|
||||
TransformList = source.Metadata.TransformList.ToList();
|
||||
TransformState = source.transformState;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using NAPS2.Config;
|
||||
using NAPS2.Ocr;
|
||||
using NAPS2.Operation;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
|
||||
@ -101,29 +102,28 @@ namespace NAPS2.Scan.Images
|
||||
return tempFilePath;
|
||||
}
|
||||
|
||||
private readonly ThumbnailRenderer thumbnailRenderer;
|
||||
private readonly IOperationFactory operationFactory;
|
||||
private readonly IOperationProgress operationProgress;
|
||||
private readonly OcrRequestQueue ocrRequestQueue;
|
||||
private readonly OcrManager ocrManager;
|
||||
|
||||
public ScannedImageHelper(ThumbnailRenderer thumbnailRenderer, IOperationFactory operationFactory, IOperationProgress operationProgress, OcrRequestQueue ocrRequestQueue, OcrManager ocrManager)
|
||||
public ScannedImageHelper(IOperationFactory operationFactory, IOperationProgress operationProgress, OcrRequestQueue ocrRequestQueue, OcrManager ocrManager)
|
||||
{
|
||||
this.thumbnailRenderer = thumbnailRenderer;
|
||||
this.operationFactory = operationFactory;
|
||||
this.operationProgress = operationProgress;
|
||||
this.ocrRequestQueue = ocrRequestQueue;
|
||||
this.ocrManager = ocrManager;
|
||||
}
|
||||
|
||||
public Bitmap PostProcessStep1(Image output, ScanProfile profile, bool supportsNativeUI = true)
|
||||
public IMemoryStorage PostProcessStep1(IMemoryStorage output, ScanProfile profile, bool supportsNativeUI = true)
|
||||
{
|
||||
double scaleFactor = 1;
|
||||
if (!profile.UseNativeUI || !supportsNativeUI)
|
||||
{
|
||||
scaleFactor = profile.AfterScanScale.ToIntScaleFactor();
|
||||
}
|
||||
var result = ImageScaleHelper.ScaleImage(output, scaleFactor);
|
||||
// TODO: Scale
|
||||
var result = output;//ImageScaleHelper.ScaleImage(output, scaleFactor);
|
||||
|
||||
if ((!profile.UseNativeUI || !supportsNativeUI) && (profile.ForcePageSize || profile.ForcePageSizeCrop))
|
||||
{
|
||||
@ -139,31 +139,31 @@ namespace NAPS2.Scan.Images
|
||||
{
|
||||
if (profile.ForcePageSizeCrop)
|
||||
{
|
||||
result = new CropTransform
|
||||
result = StorageManager.PerformTransform(result, new CropTransform
|
||||
{
|
||||
Right = (int) ((width - (float) pageDimensions.HeightInInches()) * output.HorizontalResolution),
|
||||
Bottom = (int) ((height - (float) pageDimensions.WidthInInches()) * output.VerticalResolution)
|
||||
}.Perform(result);
|
||||
Right = (int)((width - (float)pageDimensions.HeightInInches()) * output.HorizontalResolution),
|
||||
Bottom = (int)((height - (float)pageDimensions.WidthInInches()) * output.VerticalResolution)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
result.SafeSetResolution((float) (output.Width / pageDimensions.HeightInInches()),
|
||||
(float) (output.Height / pageDimensions.WidthInInches()));
|
||||
result.SetResolution((float)(output.Width / pageDimensions.HeightInInches()),
|
||||
(float)(output.Height / pageDimensions.WidthInInches()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (profile.ForcePageSizeCrop)
|
||||
{
|
||||
result = new CropTransform
|
||||
result = StorageManager.PerformTransform(result, new CropTransform
|
||||
{
|
||||
Right = (int) ((width - (float) pageDimensions.WidthInInches()) * output.HorizontalResolution),
|
||||
Bottom = (int) ((height - (float) pageDimensions.HeightInInches()) * output.VerticalResolution)
|
||||
}.Perform(result);
|
||||
Right = (int)((width - (float)pageDimensions.WidthInInches()) * output.HorizontalResolution),
|
||||
Bottom = (int)((height - (float)pageDimensions.HeightInInches()) * output.VerticalResolution)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
result.SafeSetResolution((float)(output.Width / pageDimensions.WidthInInches()), (float)(output.Height / pageDimensions.HeightInInches()));
|
||||
result.SetResolution((float)(output.Width / pageDimensions.WidthInInches()), (float)(output.Height / pageDimensions.HeightInInches()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -171,11 +171,11 @@ namespace NAPS2.Scan.Images
|
||||
return result;
|
||||
}
|
||||
|
||||
public void PostProcessStep2(ScannedImage image, Bitmap bitmap, ScanProfile profile, ScanParams scanParams, int pageNumber, bool supportsNativeUI = true)
|
||||
public void PostProcessStep2(ScannedImage image, IMemoryStorage bitmap, ScanProfile profile, ScanParams scanParams, int pageNumber, bool supportsNativeUI = true)
|
||||
{
|
||||
if (!scanParams.NoThumbnails)
|
||||
{
|
||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
||||
image.SetThumbnail(StorageManager.PerformTransform(bitmap, new ThumbnailTransform()));
|
||||
}
|
||||
if (scanParams.SkipPostProcessing)
|
||||
{
|
||||
@ -220,13 +220,13 @@ namespace NAPS2.Scan.Images
|
||||
return scanParams.DoOcr ?? (ocrEnabled && afterScanning);
|
||||
}
|
||||
|
||||
public string SaveForBackgroundOcr(Bitmap bitmap, ScanParams scanParams)
|
||||
public string SaveForBackgroundOcr(IMemoryStorage bitmap, ScanParams scanParams)
|
||||
{
|
||||
if (ShouldDoBackgroundOcr(scanParams))
|
||||
{
|
||||
string tempPath = Path.Combine(Paths.Temp, Path.GetRandomFileName());
|
||||
bitmap.Save(tempPath);
|
||||
return tempPath;
|
||||
var fileStorage = StorageManager.Convert<FileStorage>(bitmap, new StorageConvertParams { Temporary = true });
|
||||
// TODO: Maybe return the storage rather than the path
|
||||
return fileStorage.FullPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -249,14 +249,14 @@ namespace NAPS2.Scan.Images
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTransformAndUpdateThumbnail(ScannedImage image, ref Bitmap bitmap, Transform transform)
|
||||
private void AddTransformAndUpdateThumbnail(ScannedImage image, ref IMemoryStorage bitmap, Transform transform)
|
||||
{
|
||||
image.AddTransform(transform);
|
||||
var thumbnail = image.GetThumbnail();
|
||||
if (thumbnail != null)
|
||||
{
|
||||
bitmap = transform.Perform(bitmap);
|
||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
||||
bitmap = StorageManager.PerformTransform(bitmap, transform);
|
||||
image.SetThumbnail(StorageManager.PerformTransform(bitmap, new ThumbnailTransform()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NAPS2.Recovery;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
|
||||
@ -279,7 +280,7 @@ namespace NAPS2.Scan.Images
|
||||
var thumb = img.GetThumbnail();
|
||||
if (thumb != null)
|
||||
{
|
||||
img.SetThumbnail(transform.Perform(thumb));
|
||||
img.SetThumbnail(StorageManager.PerformTransform(thumb, transform));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NAPS2.ImportExport.Pdf;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
|
||||
namespace NAPS2.Scan.Images
|
||||
@ -20,7 +21,7 @@ namespace NAPS2.Scan.Images
|
||||
this.pdfRenderer = pdfRenderer;
|
||||
}
|
||||
|
||||
public async Task<Bitmap> Render(ScannedImage image, int outputSize = 0)
|
||||
public async Task<IMemoryStorage> Render(ScannedImage image, int outputSize = 0)
|
||||
{
|
||||
using (var snapshot = image.Preserve())
|
||||
{
|
||||
@ -28,38 +29,20 @@ namespace NAPS2.Scan.Images
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Bitmap> Render(ScannedImage.Snapshot snapshot, int outputSize = 0)
|
||||
public async Task<IMemoryStorage> Render(ScannedImage.Snapshot snapshot, int outputSize = 0)
|
||||
{
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
var bitmap = snapshot.Source.FileFormat == null
|
||||
? pdfRenderer.Render(snapshot.Source.RecoveryFilePath).Single()
|
||||
: new Bitmap(snapshot.Source.RecoveryFilePath);
|
||||
var storage = StorageManager.ConvertToMemory(snapshot.Source.BackingStorage, new StorageConvertParams());
|
||||
if (outputSize > 0)
|
||||
{
|
||||
bitmap = ShrinkBitmap(bitmap, outputSize);
|
||||
double scaleFactor = Math.Min(outputSize / (double)storage.Height, outputSize / (double)storage.Width);
|
||||
storage = StorageManager.PerformTransform(storage, new ScaleTransform { ScaleFactor = scaleFactor });
|
||||
}
|
||||
return Transform.PerformAll(bitmap, snapshot.TransformList);
|
||||
return StorageManager.PerformAllTransforms(storage, snapshot.TransformList);
|
||||
});
|
||||
}
|
||||
|
||||
private Bitmap ShrinkBitmap(Bitmap bitmap, int outputSize)
|
||||
{
|
||||
double scaleFactor = Math.Min(outputSize / (double)bitmap.Height, outputSize / (double)bitmap.Width);
|
||||
if (scaleFactor >= 1)
|
||||
{
|
||||
return bitmap;
|
||||
}
|
||||
var bitmap2 = new Bitmap((int)Math.Round(bitmap.Width * scaleFactor), (int)Math.Round(bitmap.Height * scaleFactor));
|
||||
using (var g = Graphics.FromImage(bitmap2))
|
||||
{
|
||||
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
g.DrawImage(bitmap, new Rectangle(Point.Empty, bitmap2.Size), new Rectangle(Point.Empty, bitmap.Size), GraphicsUnit.Pixel);
|
||||
}
|
||||
bitmap.Dispose();
|
||||
return bitmap2;
|
||||
}
|
||||
|
||||
public async Task<Stream> RenderToStream(ScannedImage image)
|
||||
{
|
||||
using (var snapshot = image.Preserve())
|
||||
|
@ -5,13 +5,10 @@ using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class FileStorage : IStorage
|
||||
public class FileStorage : IFileStorage
|
||||
{
|
||||
private readonly FileStorageManager fileStorageManager;
|
||||
|
||||
public FileStorage(FileStorageManager fileStorageManager, string fullPath)
|
||||
public FileStorage(string fullPath)
|
||||
{
|
||||
this.fileStorageManager = fileStorageManager;
|
||||
FullPath = fullPath ?? throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
|
||||
@ -22,7 +19,6 @@ namespace NAPS2.Scan.Images.Storage
|
||||
try
|
||||
{
|
||||
File.Delete(FullPath);
|
||||
fileStorageManager.Detach(FullPath);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
@ -7,15 +7,14 @@ namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class FileStorageManager
|
||||
{
|
||||
private static FileStorageManager _default = new FileStorageManager();
|
||||
|
||||
public static FileStorageManager Default
|
||||
{
|
||||
get => _default;
|
||||
set => _default = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
public virtual string NextFilePath() => Path.Combine(Paths.Temp, Path.GetRandomFileName());
|
||||
|
||||
public virtual void Attach(string path)
|
||||
{
|
||||
// TODO: Separate all this stuff out completely.
|
||||
}
|
||||
|
||||
public virtual void Detach(string path)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class GdiFileConverter : IStorageConverter<GdiStorage, FileStorage>, IStorageConverter<FileStorage, GdiStorage>
|
||||
public class GdiFileConverter :
|
||||
IStorageConverter<GdiStorage, FileStorage>,
|
||||
IStorageConverter<FileStorage, GdiStorage>
|
||||
{
|
||||
private readonly FileStorageManager fileStorageManager;
|
||||
|
||||
@ -15,12 +18,21 @@ namespace NAPS2.Scan.Images.Storage
|
||||
}
|
||||
|
||||
public FileStorage Convert(GdiStorage input, StorageConvertParams convertParams)
|
||||
{
|
||||
if (convertParams.Temporary)
|
||||
{
|
||||
var path = Path.Combine(Paths.Temp, Path.GetRandomFileName());
|
||||
input.Bitmap.Save(path);
|
||||
return new FileStorage(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Save smallest
|
||||
string ext = convertParams.HighQuality ? ".png" : ".jpg";
|
||||
string ext = convertParams.Lossless ? ".png" : ".jpg";
|
||||
var path = fileStorageManager.NextFilePath() + ext;
|
||||
input.Bitmap.Save(path);
|
||||
return new FileStorage(fileStorageManager, path);
|
||||
return new FileStorage(path);
|
||||
}
|
||||
}
|
||||
|
||||
public GdiStorage Convert(FileStorage input, StorageConvertParams convertParams) => new GdiStorage(new Bitmap(input.FullPath));
|
||||
|
@ -19,6 +19,18 @@ namespace NAPS2.Scan.Images.Storage
|
||||
|
||||
public int Height => Bitmap.Height;
|
||||
|
||||
public float HorizontalResolution => Bitmap.HorizontalResolution;
|
||||
|
||||
public float VerticalResolution => Bitmap.VerticalResolution;
|
||||
|
||||
public void SetResolution(float xDpi, float yDpi)
|
||||
{
|
||||
if (xDpi > 0 && yDpi > 0)
|
||||
{
|
||||
Bitmap.SetResolution(xDpi, yDpi);
|
||||
}
|
||||
}
|
||||
|
||||
public StoragePixelFormat PixelFormat
|
||||
{
|
||||
get
|
||||
@ -37,11 +49,13 @@ namespace NAPS2.Scan.Images.Storage
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOriginalLossless => Equals(Bitmap.RawFormat, ImageFormat.Bmp) || Equals(Bitmap.RawFormat, ImageFormat.Png);
|
||||
|
||||
public object Lock(out IntPtr scan0, out int stride)
|
||||
{
|
||||
var bitmapData = Bitmap.LockBits(new Rectangle(0, 0, Bitmap.Width, Bitmap.Height), ImageLockMode.ReadWrite, Bitmap.PixelFormat);
|
||||
scan0 = bitmapData.Scan0;
|
||||
stride = bitmapData.Stride;
|
||||
stride = Math.Abs(bitmapData.Stride);
|
||||
return bitmapData;
|
||||
}
|
||||
|
||||
@ -55,5 +69,10 @@ namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
Bitmap.Dispose();
|
||||
}
|
||||
|
||||
public IMemoryStorage Clone()
|
||||
{
|
||||
return new GdiStorage((Bitmap)Bitmap.Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,37 @@ namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class GdiStorageFactory : IMemoryStorageFactory
|
||||
{
|
||||
public IStorage FromBmpStream(Stream stream) => new GdiStorage(new Bitmap(stream));
|
||||
public IMemoryStorage Decode(Stream stream, string ext) => new GdiStorage(new Bitmap(stream));
|
||||
|
||||
public IStorage FromDimensions(int width, int height, StoragePixelFormat pixelFormat) => new GdiStorage(new Bitmap(width, height, GdiPixelFormat(pixelFormat)));
|
||||
public IMemoryStorage Decode(string path) => new GdiStorage(new Bitmap(path));
|
||||
|
||||
public IEnumerable<IMemoryStorage> DecodeMultiple(Stream stream, string ext, out int count)
|
||||
{
|
||||
var bitmap = new Bitmap(stream);
|
||||
count = bitmap.GetFrameCount(FrameDimension.Page);
|
||||
return EnumerateFrames(bitmap, count);
|
||||
}
|
||||
|
||||
public IEnumerable<IMemoryStorage> DecodeMultiple(string path, out int count)
|
||||
{
|
||||
var bitmap = new Bitmap(path);
|
||||
count = bitmap.GetFrameCount(FrameDimension.Page);
|
||||
return EnumerateFrames(bitmap, count);
|
||||
}
|
||||
|
||||
private IEnumerable<IMemoryStorage> EnumerateFrames(Bitmap bitmap, int count)
|
||||
{
|
||||
using (bitmap)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
bitmap.SelectActiveFrame(FrameDimension.Page, i);
|
||||
yield return new GdiStorage((Bitmap) bitmap.Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMemoryStorage FromDimensions(int width, int height, StoragePixelFormat pixelFormat) => new GdiStorage(new Bitmap(width, height, GdiPixelFormat(pixelFormat)));
|
||||
|
||||
private PixelFormat GdiPixelFormat(StoragePixelFormat pixelFormat)
|
||||
{
|
||||
|
435
NAPS2.Sdk/Scan/Images/Storage/GdiTransformer.cs
Normal file
435
NAPS2.Sdk/Scan/Images/Storage/GdiTransformer.cs
Normal file
@ -0,0 +1,435 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class GdiTransformer :
|
||||
ITransformer<GdiStorage, BrightnessTransform>,
|
||||
ITransformer<GdiStorage, ContrastTransform>,
|
||||
ITransformer<GdiStorage, TrueContrastTransform>,
|
||||
ITransformer<GdiStorage, HueTransform>,
|
||||
ITransformer<GdiStorage, SaturationTransform>,
|
||||
ITransformer<GdiStorage, SharpenTransform>,
|
||||
ITransformer<GdiStorage, RotationTransform>,
|
||||
ITransformer<GdiStorage, CropTransform>,
|
||||
ITransformer<GdiStorage, BlackWhiteTransform>,
|
||||
ITransformer<GdiStorage, ThumbnailTransform>,
|
||||
ITransformer<GdiStorage, ScaleTransform>
|
||||
{
|
||||
public GdiStorage PerformTransform(GdiStorage storage, BrightnessTransform transform)
|
||||
{
|
||||
var bitmap = storage.Bitmap;
|
||||
float brightnessAdjusted = transform.Brightness / 1000f;
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
UnsafeImageOps.ChangeBrightness(bitmap, brightnessAdjusted);
|
||||
return new GdiStorage(bitmap);
|
||||
}
|
||||
|
||||
public GdiStorage PerformTransform(GdiStorage storage, ContrastTransform transform)
|
||||
{
|
||||
float contrastAdjusted = transform.Contrast / 1000f + 1.0f;
|
||||
|
||||
var bitmap = storage.Bitmap;
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
using (var g = Graphics.FromImage(bitmap))
|
||||
{
|
||||
var attrs = new ImageAttributes();
|
||||
attrs.SetColorMatrix(new ColorMatrix
|
||||
{
|
||||
Matrix00 = contrastAdjusted,
|
||||
Matrix11 = contrastAdjusted,
|
||||
Matrix22 = contrastAdjusted
|
||||
});
|
||||
g.DrawImage(bitmap,
|
||||
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
|
||||
0,
|
||||
0,
|
||||
bitmap.Width,
|
||||
bitmap.Height,
|
||||
GraphicsUnit.Pixel,
|
||||
attrs);
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
||||
public GdiStorage PerformTransform(GdiStorage storage, TrueContrastTransform transform)
|
||||
{
|
||||
// convert +/-1000 input range to a logarithmic scaled multiplier
|
||||
float contrastAdjusted = (float)Math.Pow(2.718281f, transform.Contrast / 500.0f);
|
||||
// see http://docs.rainmeter.net/tips/colormatrix-guide/ for offset & matrix calculation
|
||||
float offset = (1.0f - contrastAdjusted) / 2.0f;
|
||||
|
||||
var bitmap = storage.Bitmap;
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
UnsafeImageOps.ChangeContrast(bitmap, contrastAdjusted, offset);
|
||||
// TODO: Actually need to create a new storage. Change signature of EnsurePixelFormat.
|
||||
return storage;
|
||||
}
|
||||
|
||||
public GdiStorage PerformTransform(GdiStorage storage, HueTransform transform)
|
||||
{
|
||||
if (storage.PixelFormat != StoragePixelFormat.RGB24 && storage.PixelFormat != StoragePixelFormat.ARGB32)
|
||||
{
|
||||
// No need to handle 1bpp since hue shifts are null transforms
|
||||
return storage;
|
||||
}
|
||||
|
||||
float hueShiftAdjusted = transform.HueShift / 2000f * 360;
|
||||
if (hueShiftAdjusted < 0)
|
||||
{
|
||||
hueShiftAdjusted += 360;
|
||||
}
|
||||
|
||||
UnsafeImageOps.HueShift(storage.Bitmap, hueShiftAdjusted);
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
public GdiStorage PerformTransform(GdiStorage storage, SaturationTransform transform)
|
||||
{
|
||||
double saturationAdjusted = transform.Saturation / 1000.0 + 1;
|
||||
|
||||
var bitmap = storage.Bitmap;
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
int bytesPerPixel;
|
||||
if (bitmap.PixelFormat == PixelFormat.Format24bppRgb)
|
||||
{
|
||||
bytesPerPixel = 3;
|
||||
}
|
||||
else if (bitmap.PixelFormat == PixelFormat.Format32bppArgb)
|
||||
{
|
||||
bytesPerPixel = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
|
||||
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
|
||||
var stride = Math.Abs(data.Stride);
|
||||
for (int y = 0; y < data.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < data.Width; x++)
|
||||
{
|
||||
int r = Marshal.ReadByte(data.Scan0 + stride * y + x * bytesPerPixel);
|
||||
int g = Marshal.ReadByte(data.Scan0 + stride * y + x * bytesPerPixel + 1);
|
||||
int b = Marshal.ReadByte(data.Scan0 + stride * y + x * bytesPerPixel + 2);
|
||||
|
||||
Color c = Color.FromArgb(255, r, g, b);
|
||||
ColorHelper.ColorToHSL(c, out double h, out double s, out double v);
|
||||
|
||||
s = Math.Min(s * saturationAdjusted, 1);
|
||||
|
||||
c = ColorHelper.ColorFromHSL(h, s, v);
|
||||
|
||||
Marshal.WriteByte(data.Scan0 + stride * y + x * bytesPerPixel, c.R);
|
||||
Marshal.WriteByte(data.Scan0 + stride * y + x * bytesPerPixel + 1, c.G);
|
||||
Marshal.WriteByte(data.Scan0 + stride * y + x * bytesPerPixel + 2, c.B);
|
||||
}
|
||||
}
|
||||
bitmap.UnlockBits(data);
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
public GdiStorage PerformTransform(GdiStorage storage, SharpenTransform transform)
|
||||
{
|
||||
double sharpnessAdjusted = transform.Sharpness / 1000.0;
|
||||
|
||||
var bitmap = storage.Bitmap;
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
int bytesPerPixel;
|
||||
if (bitmap.PixelFormat == PixelFormat.Format24bppRgb)
|
||||
{
|
||||
bytesPerPixel = 3;
|
||||
}
|
||||
else if (bitmap.PixelFormat == PixelFormat.Format32bppArgb)
|
||||
{
|
||||
bytesPerPixel = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/17596299
|
||||
|
||||
int width = bitmap.Width;
|
||||
int height = bitmap.Height;
|
||||
|
||||
// Create sharpening filter.
|
||||
const int filterSize = 5;
|
||||
|
||||
var filter = new double[,]
|
||||
{
|
||||
{-1, -1, -1, -1, -1},
|
||||
{-1, 2, 2, 2, -1},
|
||||
{-1, 2, 16, 2, -1},
|
||||
{-1, 2, 2, 2, -1},
|
||||
{-1, -1, -1, -1, -1}
|
||||
};
|
||||
|
||||
double bias = 1.0 - sharpnessAdjusted;
|
||||
double factor = sharpnessAdjusted / 16.0;
|
||||
|
||||
const int s = filterSize / 2;
|
||||
|
||||
var result = new Color[bitmap.Width, bitmap.Height];
|
||||
|
||||
// Lock image bits for read/write.
|
||||
BitmapData pbits = bitmap.LockBits(new Rectangle(0, 0, width, height),
|
||||
ImageLockMode.ReadWrite,
|
||||
bitmap.PixelFormat);
|
||||
|
||||
// Declare an array to hold the bytes of the bitmap.
|
||||
int bytes = pbits.Stride * height;
|
||||
var rgbValues = new byte[bytes];
|
||||
|
||||
// Copy the RGB values into the array.
|
||||
Marshal.Copy(pbits.Scan0, rgbValues, 0, bytes);
|
||||
|
||||
int rgb;
|
||||
// Fill the color array with the new sharpened color values.
|
||||
for (int x = s; x < width - s; x++)
|
||||
{
|
||||
for (int y = s; y < height - s; y++)
|
||||
{
|
||||
double red = 0.0, green = 0.0, blue = 0.0;
|
||||
|
||||
for (int filterX = 0; filterX < filterSize; filterX++)
|
||||
{
|
||||
for (int filterY = 0; filterY < filterSize; filterY++)
|
||||
{
|
||||
int imageX = (x - s + filterX + width) % width;
|
||||
int imageY = (y - s + filterY + height) % height;
|
||||
|
||||
rgb = imageY * pbits.Stride + bytesPerPixel * imageX;
|
||||
|
||||
red += rgbValues[rgb + 2] * filter[filterX, filterY];
|
||||
green += rgbValues[rgb + 1] * filter[filterX, filterY];
|
||||
blue += rgbValues[rgb + 0] * filter[filterX, filterY];
|
||||
}
|
||||
|
||||
rgb = y * pbits.Stride + bytesPerPixel * x;
|
||||
|
||||
int r = Math.Min(Math.Max((int)(factor * red + (bias * rgbValues[rgb + 2])), 0), 255);
|
||||
int g = Math.Min(Math.Max((int)(factor * green + (bias * rgbValues[rgb + 1])), 0), 255);
|
||||
int b = Math.Min(Math.Max((int)(factor * blue + (bias * rgbValues[rgb + 0])), 0), 255);
|
||||
|
||||
result[x, y] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the image with the sharpened pixels.
|
||||
for (int x = s; x < width - s; x++)
|
||||
{
|
||||
for (int y = s; y < height - s; y++)
|
||||
{
|
||||
rgb = y * pbits.Stride + bytesPerPixel * x;
|
||||
|
||||
rgbValues[rgb + 2] = result[x, y].R;
|
||||
rgbValues[rgb + 1] = result[x, y].G;
|
||||
rgbValues[rgb + 0] = result[x, y].B;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the RGB values back to the bitmap.
|
||||
Marshal.Copy(rgbValues, 0, pbits.Scan0, bytes);
|
||||
// Release image bits.
|
||||
bitmap.UnlockBits(pbits);
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
public GdiStorage PerformTransform(GdiStorage storage, RotationTransform transform)
|
||||
{
|
||||
|
||||
if (Math.Abs(transform.Angle - 0.0) < RotationTransform.TOLERANCE)
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
if (Math.Abs(transform.Angle - 90.0) < RotationTransform.TOLERANCE)
|
||||
{
|
||||
storage.Bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
|
||||
return storage;
|
||||
}
|
||||
if (Math.Abs(transform.Angle - 180.0) < RotationTransform.TOLERANCE)
|
||||
{
|
||||
storage.Bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
|
||||
return storage;
|
||||
}
|
||||
if (Math.Abs(transform.Angle - 270.0) < RotationTransform.TOLERANCE)
|
||||
{
|
||||
storage.Bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
|
||||
return storage;
|
||||
}
|
||||
Bitmap result;
|
||||
if (transform.Angle > 45.0 && transform.Angle < 135.0 || transform.Angle > 225.0 && transform.Angle < 315.0)
|
||||
{
|
||||
result = new Bitmap(storage.Height, storage.Width);
|
||||
result.SafeSetResolution(storage.VerticalResolution, storage.HorizontalResolution);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new Bitmap(storage.Width, storage.Height);
|
||||
result.SafeSetResolution(storage.HorizontalResolution, storage.VerticalResolution);
|
||||
}
|
||||
using (var g = Graphics.FromImage(result))
|
||||
{
|
||||
g.Clear(Color.White);
|
||||
g.TranslateTransform(result.Width / 2.0f, result.Height / 2.0f);
|
||||
g.RotateTransform((float)transform.Angle);
|
||||
g.TranslateTransform(-storage.Width / 2.0f, -storage.Height / 2.0f);
|
||||
g.DrawImage(storage.Bitmap, new Rectangle(0, 0, storage.Width, storage.Height));
|
||||
}
|
||||
OptimizePixelFormat(storage.Bitmap, ref result);
|
||||
storage.Dispose();
|
||||
return new GdiStorage(result);
|
||||
}
|
||||
|
||||
public GdiStorage PerformTransform(GdiStorage storage, CropTransform transform)
|
||||
{
|
||||
double xScale = storage.Width / (double)(transform.OriginalWidth ?? storage.Width),
|
||||
yScale = storage.Height / (double)(transform.OriginalHeight ?? storage.Height);
|
||||
|
||||
int width = Math.Max(storage.Width - (int)Math.Round((transform.Left + transform.Right) * xScale), 1);
|
||||
int height = Math.Max(storage.Height - (int)Math.Round((transform.Top + transform.Bottom) * yScale), 1);
|
||||
var result = new Bitmap(width, height, PixelFormat.Format24bppRgb);
|
||||
result.SafeSetResolution(storage.HorizontalResolution, storage.VerticalResolution);
|
||||
using (var g = Graphics.FromImage(result))
|
||||
{
|
||||
g.Clear(Color.White);
|
||||
int x = (int) Math.Round(-transform.Left * xScale);
|
||||
int y = (int) Math.Round(-transform.Top * yScale);
|
||||
g.DrawImage(storage.Bitmap, new Rectangle(x, y, storage.Width, storage.Height));
|
||||
}
|
||||
OptimizePixelFormat(storage.Bitmap, ref result);
|
||||
storage.Dispose();
|
||||
return new GdiStorage(result);
|
||||
}
|
||||
|
||||
public GdiStorage PerformTransform(GdiStorage storage, ScaleTransform transform)
|
||||
{
|
||||
double realWidth = storage.Width / transform.ScaleFactor;
|
||||
double realHeight = storage.Height / transform.ScaleFactor;
|
||||
|
||||
double horizontalRes = storage.HorizontalResolution / transform.ScaleFactor;
|
||||
double verticalRes = storage.VerticalResolution / transform.ScaleFactor;
|
||||
|
||||
var result = new Bitmap((int)realWidth, (int)realHeight, PixelFormat.Format24bppRgb);
|
||||
using (Graphics g = Graphics.FromImage(result))
|
||||
{
|
||||
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
g.DrawImage(storage.Bitmap, 0, 0, (int)realWidth, (int)realHeight);
|
||||
result.SafeSetResolution((float)horizontalRes, (float)verticalRes);
|
||||
return new GdiStorage(result);
|
||||
}
|
||||
}
|
||||
|
||||
public GdiStorage PerformTransform(GdiStorage storage, BlackWhiteTransform transform)
|
||||
{
|
||||
if (storage.PixelFormat != StoragePixelFormat.RGB24 && storage.PixelFormat != StoragePixelFormat.ARGB32)
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
|
||||
var monoBitmap = UnsafeImageOps.ConvertTo1Bpp(storage.Bitmap, transform.Threshold);
|
||||
storage.Dispose();
|
||||
|
||||
return new GdiStorage(monoBitmap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bitmap resized to fit within a thumbnail rectangle, including a border around the picture.
|
||||
/// </summary>
|
||||
/// <param name="storage">The bitmap to resize.</param>
|
||||
/// <param name="transform">The maximum width and height of the thumbnail.</param>
|
||||
/// <returns>The thumbnail bitmap.</returns>
|
||||
public GdiStorage PerformTransform(GdiStorage storage, ThumbnailTransform transform)
|
||||
{
|
||||
var result = new Bitmap(transform.Size, transform.Size);
|
||||
using (Graphics g = Graphics.FromImage(result))
|
||||
{
|
||||
// The location and dimensions of the old bitmap, scaled and positioned within the thumbnail bitmap
|
||||
int left, top, width, height;
|
||||
|
||||
// We want a nice thumbnail, so use the maximum quality interpolation
|
||||
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
|
||||
if (storage.Width > storage.Height)
|
||||
{
|
||||
// Fill the new bitmap's width
|
||||
width = transform.Size;
|
||||
left = 0;
|
||||
// Scale the drawing height to match the original bitmap's aspect ratio
|
||||
height = (int)(storage.Height * (transform.Size / (double)storage.Width));
|
||||
// Center the drawing vertically
|
||||
top = (transform.Size - height) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fill the new bitmap's height
|
||||
height = transform.Size;
|
||||
top = 0;
|
||||
// Scale the drawing width to match the original bitmap's aspect ratio
|
||||
width = (int)(storage.Width * (transform.Size / (double)storage.Height));
|
||||
// Center the drawing horizontally
|
||||
left = (transform.Size - width) / 2;
|
||||
}
|
||||
|
||||
// Draw the original bitmap onto the new bitmap, using the calculated location and dimensions
|
||||
// Note that there may be some padding if the aspect ratios don't match
|
||||
var destRect = new RectangleF(left, top, width, height);
|
||||
var srcRect = new RectangleF(0, 0, storage.Width, storage.Height);
|
||||
g.DrawImage(storage.Bitmap, destRect, srcRect, GraphicsUnit.Pixel);
|
||||
// Draw a border around the orignal bitmap's content, inside the padding
|
||||
g.DrawRectangle(Pens.Black, left, top, width - 1, height - 1);
|
||||
}
|
||||
|
||||
return new GdiStorage(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the provided bitmap is 1-bit (black and white), replace it with a 24-bit bitmap so that image transforms will work. If the bitmap is replaced, the original is disposed.
|
||||
/// </summary>
|
||||
/// <param name="bitmap">The bitmap that may be replaced.</param>
|
||||
protected static void EnsurePixelFormat(ref Bitmap bitmap)
|
||||
{
|
||||
if (bitmap.PixelFormat == PixelFormat.Format1bppIndexed)
|
||||
{
|
||||
// Copy B&W over to grayscale
|
||||
var bitmap2 = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format24bppRgb);
|
||||
bitmap2.SafeSetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
|
||||
using (var g = Graphics.FromImage(bitmap2))
|
||||
{
|
||||
g.DrawImage(bitmap, 0, 0);
|
||||
}
|
||||
bitmap.Dispose();
|
||||
bitmap = bitmap2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the original bitmap is 1-bit (black and white), optimize the result by making it 1-bit too.
|
||||
/// </summary>
|
||||
/// <param name="original">The original bitmap that is used to determine whether the result should be black and white.</param>
|
||||
/// <param name="result">The result that may be replaced.</param>
|
||||
protected static void OptimizePixelFormat(Bitmap original, ref Bitmap result)
|
||||
{
|
||||
if (original.PixelFormat == PixelFormat.Format1bppIndexed)
|
||||
{
|
||||
var bitmap2 = (Bitmap)BitmapHelper.CopyToBpp(result, 1).Clone();
|
||||
result.Dispose();
|
||||
result = bitmap2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
NAPS2.Sdk/Scan/Images/Storage/IFileStorage.cs
Normal file
11
NAPS2.Sdk/Scan/Images/Storage/IFileStorage.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public interface IFileStorage : IStorage
|
||||
{
|
||||
string FullPath { get; }
|
||||
}
|
||||
}
|
26
NAPS2.Sdk/Scan/Images/Storage/IImageMetadata.cs
Normal file
26
NAPS2.Sdk/Scan/Images/Storage/IImageMetadata.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public interface IImageMetadata : IDisposable
|
||||
{
|
||||
List<Transform> TransformList { get; set; }
|
||||
|
||||
int Index { get; set; }
|
||||
|
||||
ScanBitDepth BitDepth { get; set; }
|
||||
|
||||
bool Lossless { get; set; }
|
||||
|
||||
void Commit();
|
||||
|
||||
bool CanSerialize { get; }
|
||||
|
||||
byte[] Serialize(IStorage storage);
|
||||
|
||||
IStorage Deserialize(byte[] serializedData);
|
||||
}
|
||||
}
|
11
NAPS2.Sdk/Scan/Images/Storage/IImageMetadataFactory.cs
Normal file
11
NAPS2.Sdk/Scan/Images/Storage/IImageMetadataFactory.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public interface IImageMetadataFactory
|
||||
{
|
||||
IImageMetadata CreateMetadata(IStorage storage);
|
||||
}
|
||||
}
|
@ -1,19 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
// TODO: Maybe just call this IImage.
|
||||
public interface IMemoryStorage : IStorage
|
||||
{
|
||||
int Width { get; }
|
||||
|
||||
int Height { get; }
|
||||
|
||||
float HorizontalResolution { get; }
|
||||
|
||||
float VerticalResolution { get; }
|
||||
|
||||
void SetResolution(float xDpi, float yDpi);
|
||||
|
||||
StoragePixelFormat PixelFormat { get; }
|
||||
|
||||
bool IsOriginalLossless { get; }
|
||||
|
||||
object Lock(out IntPtr scan0, out int stride);
|
||||
|
||||
void Unlock(object state);
|
||||
|
||||
IMemoryStorage Clone();
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,40 @@ namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public interface IMemoryStorageFactory
|
||||
{
|
||||
IStorage FromBmpStream(Stream stream);
|
||||
/// <summary>
|
||||
/// Decodes an image from the given stream and file extension.
|
||||
/// </summary>
|
||||
/// <param name="stream">The image data, in a common format (JPEG, PNG, etc).</param>
|
||||
/// <param name="ext">A file extension hinting at the image format. When possible, the contents of the stream should be used to definitively determine the image format.</param>
|
||||
/// <returns></returns>
|
||||
IMemoryStorage Decode(Stream stream, string ext);
|
||||
|
||||
IStorage FromDimensions(int width, int height, StoragePixelFormat pixelFormat);
|
||||
/// <summary>
|
||||
/// Decodes an image from the given file path.
|
||||
/// </summary>
|
||||
/// <param name="path">The image path.</param>
|
||||
/// <returns></returns>
|
||||
IMemoryStorage Decode(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Decodes an image from the given stream and file extension.
|
||||
/// If there are multiple images (e.g. TIFF), multiple results will be returned;
|
||||
/// however, only the enumerator's current IStorage is guaranteed to be valid.
|
||||
/// </summary>
|
||||
/// <param name="stream">The image data, in a common format (JPEG, PNG, etc).</param>
|
||||
/// <param name="ext">A file extension hinting at the image format. When possible, the contents of the stream should be used to definitively determine the image format.</param>
|
||||
/// <param name="count">The number of returned images.</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IMemoryStorage> DecodeMultiple(Stream stream, string ext, out int count);
|
||||
|
||||
/// <summary>
|
||||
/// Decodes an image from the given file path.
|
||||
/// </summary>
|
||||
/// <param name="path">The image path.</param>
|
||||
/// <param name="count">The number of returned images.</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IMemoryStorage> DecodeMultiple(string path, out int count);
|
||||
|
||||
IMemoryStorage FromDimensions(int width, int height, StoragePixelFormat pixelFormat);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public interface IStorageConverter<in TStorage1, out TStorage2>
|
||||
public interface IStorageConverter<in TStorage1, out TStorage2> where TStorage1 : IStorage where TStorage2 : IStorage
|
||||
{
|
||||
TStorage2 Convert(TStorage1 input, StorageConvertParams convertParams);
|
||||
}
|
||||
|
12
NAPS2.Sdk/Scan/Images/Storage/ITransformer.cs
Normal file
12
NAPS2.Sdk/Scan/Images/Storage/ITransformer.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public interface ITransformer<TStorage, in TTransform> where TStorage : IMemoryStorage where TTransform : Transform
|
||||
{
|
||||
TStorage PerformTransform(TStorage storage, TTransform transform);
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class PdfFileStorage : IStorage
|
||||
public class PdfFileStorage : IFileStorage
|
||||
{
|
||||
public PdfFileStorage(string fullPath)
|
||||
{
|
||||
|
73
NAPS2.Sdk/Scan/Images/Storage/RecoverableImageMetadata.cs
Normal file
73
NAPS2.Sdk/Scan/Images/Storage/RecoverableImageMetadata.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NAPS2.Recovery;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class RecoverableImageMetadata : IImageMetadata
|
||||
{
|
||||
private readonly RecoveryStorageManager rsm;
|
||||
private RecoveryIndexImage indexImage;
|
||||
|
||||
public RecoverableImageMetadata(RecoveryStorageManager rsm, RecoveryIndexImage indexImage)
|
||||
{
|
||||
this.rsm = rsm;
|
||||
// TODO: Maybe not a constructor param?
|
||||
this.indexImage = indexImage;
|
||||
}
|
||||
|
||||
public List<Transform> TransformList
|
||||
{
|
||||
get => indexImage.TransformList;
|
||||
set => indexImage.TransformList = value;
|
||||
}
|
||||
|
||||
public int Index
|
||||
{
|
||||
get => rsm.Index.Images.IndexOf(indexImage);
|
||||
set
|
||||
{
|
||||
// TODO: Locking
|
||||
rsm.Index.Images.Remove(indexImage);
|
||||
rsm.Index.Images.Insert(value, indexImage);
|
||||
}
|
||||
}
|
||||
|
||||
public ScanBitDepth BitDepth
|
||||
{
|
||||
get => indexImage.BitDepth;
|
||||
set => indexImage.BitDepth = value;
|
||||
}
|
||||
|
||||
public bool Lossless
|
||||
{
|
||||
get => indexImage.HighQuality;
|
||||
set => indexImage.HighQuality = value;
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
rsm.Commit();
|
||||
}
|
||||
|
||||
public bool CanSerialize => true;
|
||||
|
||||
public byte[] Serialize(IStorage storage)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IStorage Deserialize(byte[] serializedData)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
rsm.Index.Images.Remove(indexImage);
|
||||
// TODO: Commit?
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NAPS2.Config;
|
||||
using NAPS2.Recovery;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class RecoveryStorageManager : FileStorageManager
|
||||
public class RecoveryStorageManager : FileStorageManager, IImageMetadataFactory
|
||||
{
|
||||
public const string LOCK_FILE_NAME = ".lock";
|
||||
|
||||
private readonly string recoveryFolderPath;
|
||||
|
||||
private int fileNumber;
|
||||
private bool folderCreated;
|
||||
private FileInfo folderLockFile;
|
||||
private Stream folderLock;
|
||||
private ConfigManager<RecoveryIndex> indexConfigManager;
|
||||
|
||||
public RecoveryStorageManager(string recoveryFolderPath)
|
||||
{
|
||||
indexConfigManager = new ConfigManager<RecoveryIndex>("index.xml", recoveryFolderPath, null, RecoveryIndex.Create);
|
||||
this.recoveryFolderPath = recoveryFolderPath;
|
||||
}
|
||||
|
||||
public override void Detach(string path)
|
||||
public RecoveryIndex Index
|
||||
{
|
||||
//lock (this)
|
||||
//{
|
||||
// indexConfigManager.Config.Images.Remove(IndexImage);
|
||||
// indexConfigManager.Save();
|
||||
//}
|
||||
get
|
||||
{
|
||||
EnsureFolderCreated();
|
||||
return indexConfigManager.Config;
|
||||
}
|
||||
}
|
||||
|
||||
public override string NextFilePath()
|
||||
{
|
||||
string fileName = $"{Process.GetCurrentProcess().Id}_{(++fileNumber).ToString("D5", CultureInfo.InvariantCulture)}";
|
||||
return Path.Combine(recoveryFolderPath, fileName);
|
||||
}
|
||||
|
||||
private void EnsureFolderCreated()
|
||||
{
|
||||
if (!folderCreated)
|
||||
{
|
||||
var folder = new DirectoryInfo(recoveryFolderPath);
|
||||
folder.Create();
|
||||
folderLockFile = new FileInfo(Path.Combine(recoveryFolderPath, LOCK_FILE_NAME));
|
||||
folderLock = folderLockFile.Open(FileMode.CreateNew, FileAccess.Write, FileShare.None);
|
||||
indexConfigManager = new ConfigManager<RecoveryIndex>("index.xml", recoveryFolderPath, null, RecoveryIndex.Create);
|
||||
folderCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
// TODO: Clean up when all contents are removed
|
||||
|
||||
EnsureFolderCreated();
|
||||
indexConfigManager.Save();
|
||||
}
|
||||
|
||||
public IImageMetadata CreateMetadata(IStorage storage)
|
||||
{
|
||||
var fileStorage = storage as IFileStorage;
|
||||
if (fileStorage == null)
|
||||
{
|
||||
throw new ArgumentException("RecoveryStorageManager can only used with IFileStorage.");
|
||||
}
|
||||
return new RecoverableImageMetadata(this, new RecoveryIndexImage
|
||||
{
|
||||
FileName = Path.GetFileName(fileStorage.FullPath)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,10 @@ namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class StorageConvertParams
|
||||
{
|
||||
public bool HighQuality { get; set; }
|
||||
public bool Temporary { get; set; }
|
||||
|
||||
public bool Lossless { get; set; }
|
||||
|
||||
public int LossyQuality { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
@ -18,38 +19,84 @@ namespace NAPS2.Scan.Images.Storage
|
||||
|
||||
public static IMemoryStorageFactory MemoryStorageFactory { get; set; } = new GdiStorageFactory();
|
||||
|
||||
public static IImageMetadataFactory ImageMetadataFactory { get; set; }
|
||||
|
||||
private static readonly Dictionary<(Type, Type), (object, MethodInfo)> Transformers = new Dictionary<(Type, Type), (object, MethodInfo)>();
|
||||
|
||||
public static void RegisterTransformer(object transformer)
|
||||
{
|
||||
foreach (var interfaceType in transformer.GetType().GetInterfaces().Where(x => x.Name == "ITransformer"))
|
||||
{
|
||||
var genericArgs = interfaceType.GetGenericArguments();
|
||||
Transformers.Add((genericArgs[0], genericArgs[1]), (transformer, interfaceType.GetMethod("PerformTransform")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static IMemoryStorage PerformTransform(IMemoryStorage storage, Transform transform)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (transformer, perform) = Transformers[(storage.GetType(), transform.GetType())];
|
||||
return (IMemoryStorage)perform.Invoke(transformer, new object[] { storage, transform });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
throw new ArgumentException($"No transformer exists for {storage.GetType().Name} and {transform.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
public static IMemoryStorage PerformAllTransforms(IMemoryStorage storage, IEnumerable<Transform> transforms)
|
||||
{
|
||||
return transforms.Aggregate(storage, PerformTransform);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<(Type, Type), (object, MethodInfo)> Converters = new Dictionary<(Type, Type), (object, MethodInfo)>();
|
||||
|
||||
public static void RegisterConverter<TStorage1, TStorage2>(IStorageConverter<TStorage1, TStorage2> converter)
|
||||
public static void RegisterConverter<TStorage1, TStorage2>(IStorageConverter<TStorage1, TStorage2> converter) where TStorage1 : IStorage where TStorage2 : IStorage
|
||||
{
|
||||
Converters.Add((typeof(TStorage1), typeof(TStorage2)), (converter, typeof(IStorageConverter<TStorage1, TStorage2>).GetMethod("Convert")));
|
||||
}
|
||||
|
||||
public static IStorage ConvertToBacking(IStorage storage)
|
||||
public static IStorage ConvertToBacking(IStorage storage, StorageConvertParams convertParams)
|
||||
{
|
||||
if (BackingStorageTypes.Contains(storage.GetType()))
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
return Convert(storage, PreferredBackingStorageType);
|
||||
return Convert(storage, PreferredBackingStorageType, convertParams);
|
||||
}
|
||||
|
||||
public static IStorage ConvertToMemory(IStorage storage)
|
||||
public static IMemoryStorage ConvertToMemory(IStorage storage, StorageConvertParams convertParams)
|
||||
{
|
||||
if (storage is IMemoryStorage)
|
||||
if (storage is IMemoryStorage memStorage)
|
||||
{
|
||||
return memStorage;
|
||||
}
|
||||
return (IMemoryStorage)Convert(storage, PreferredMemoryStorageType, convertParams);
|
||||
}
|
||||
|
||||
public static TStorage Convert<TStorage>(IStorage storage)
|
||||
{
|
||||
return (TStorage)Convert(storage, typeof(TStorage), new StorageConvertParams());
|
||||
}
|
||||
|
||||
public static TStorage Convert<TStorage>(IStorage storage, StorageConvertParams convertParams)
|
||||
{
|
||||
return (TStorage)Convert(storage, typeof(TStorage), convertParams);
|
||||
}
|
||||
|
||||
public static IStorage Convert(IStorage storage, Type type, StorageConvertParams convertParams)
|
||||
{
|
||||
if (storage.GetType() == type)
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
return Convert(storage, PreferredMemoryStorageType);
|
||||
}
|
||||
|
||||
public static IStorage Convert(IStorage storage, Type type)
|
||||
{
|
||||
// TODO: Dispose old storage?
|
||||
// TODO: Dispose old storage? Consider ownership. Possibility: Clone/Dispose ref counts.
|
||||
try
|
||||
{
|
||||
var (converter, convert) = Converters[(storage.GetType(), type)];
|
||||
return (IStorage) convert.Invoke(converter, new object[] {storage});
|
||||
return (IStorage)convert.Invoke(converter, new object[] { storage, convertParams });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
@ -62,6 +109,8 @@ namespace NAPS2.Scan.Images.Storage
|
||||
var gdiFileConverter = new GdiFileConverter(new FileStorageManager());
|
||||
RegisterConverter<GdiStorage, FileStorage>(gdiFileConverter);
|
||||
RegisterConverter<FileStorage, GdiStorage>(gdiFileConverter);
|
||||
var gdiTransformer = new GdiTransformer();
|
||||
RegisterTransformer(gdiTransformer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
NAPS2.Sdk/Scan/Images/Storage/StubImageMetadata.cs
Normal file
32
NAPS2.Sdk/Scan/Images/Storage/StubImageMetadata.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class StubImageMetadata : IImageMetadata
|
||||
{
|
||||
public List<Transform> TransformList { get; set; }
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public ScanBitDepth BitDepth { get; set; }
|
||||
|
||||
public bool Lossless { get; set; }
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
}
|
||||
|
||||
public bool CanSerialize => true;
|
||||
|
||||
public byte[] Serialize(IStorage storage) => throw new InvalidOperationException();
|
||||
|
||||
public IStorage Deserialize(byte[] serializedData) => throw new InvalidOperationException();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
11
NAPS2.Sdk/Scan/Images/Storage/StubImageMetadataFactory.cs
Normal file
11
NAPS2.Sdk/Scan/Images/Storage/StubImageMetadataFactory.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Storage
|
||||
{
|
||||
public class StubImageMetadataFactory : IImageMetadataFactory
|
||||
{
|
||||
public IImageMetadata CreateMetadata(IStorage storage) => new StubImageMetadata();
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
|
||||
namespace NAPS2.Scan.Images
|
||||
{
|
||||
@ -16,23 +17,28 @@ namespace NAPS2.Scan.Images
|
||||
private const double COVERAGE_THRESHOLD_MIN = 0.00;
|
||||
private const double COVERAGE_THRESHOLD_MAX = 0.01;
|
||||
|
||||
public bool IsBlank(Bitmap bitmap, int whiteThresholdNorm, int coverageThresholdNorm)
|
||||
public bool IsBlank(IMemoryStorage bitmap, int whiteThresholdNorm, int coverageThresholdNorm)
|
||||
{
|
||||
if (bitmap.PixelFormat == PixelFormat.Format1bppIndexed)
|
||||
if (bitmap.PixelFormat == StoragePixelFormat.BW1)
|
||||
{
|
||||
using (var bitmap2 = BitmapHelper.CopyToBpp(bitmap, 8))
|
||||
// TODO: Make more generic
|
||||
if (!(bitmap is GdiStorage gdiStorage))
|
||||
{
|
||||
return IsBlankRGB(bitmap2, whiteThresholdNorm, coverageThresholdNorm);
|
||||
throw new InvalidOperationException("Patch code detection only supported for GdiStorage");
|
||||
}
|
||||
using (var bitmap2 = BitmapHelper.CopyToBpp(gdiStorage.Bitmap, 8))
|
||||
{
|
||||
return IsBlankRGB(new GdiStorage(bitmap2), whiteThresholdNorm, coverageThresholdNorm);
|
||||
}
|
||||
}
|
||||
if (bitmap.PixelFormat != PixelFormat.Format24bppRgb)
|
||||
if (bitmap.PixelFormat != StoragePixelFormat.RGB24)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return IsBlankRGB(bitmap, whiteThresholdNorm, coverageThresholdNorm);
|
||||
}
|
||||
|
||||
private static bool IsBlankRGB(Bitmap bitmap, int whiteThresholdNorm, int coverageThresholdNorm)
|
||||
private static bool IsBlankRGB(IMemoryStorage bitmap, int whiteThresholdNorm, int coverageThresholdNorm)
|
||||
{
|
||||
var whiteThreshold = (int)Math.Round(WHITE_THRESHOLD_MIN + (whiteThresholdNorm / 100.0) * (WHITE_THRESHOLD_MAX - WHITE_THRESHOLD_MIN));
|
||||
var coverageThreshold = COVERAGE_THRESHOLD_MIN + (coverageThresholdNorm / 100.0) * (COVERAGE_THRESHOLD_MAX - COVERAGE_THRESHOLD_MIN);
|
||||
@ -40,14 +46,13 @@ namespace NAPS2.Scan.Images
|
||||
long totalPixels = bitmap.Width * bitmap.Height;
|
||||
long matchPixels = 0;
|
||||
|
||||
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
|
||||
var stride = Math.Abs(data.Stride);
|
||||
var bytes = new byte[stride * data.Height];
|
||||
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
|
||||
bitmap.UnlockBits(data);
|
||||
for (int x = 0; x < data.Width; x++)
|
||||
var data = bitmap.Lock(out var scan0, out var stride);
|
||||
var bytes = new byte[stride * bitmap.Height];
|
||||
Marshal.Copy(scan0, bytes, 0, bytes.Length);
|
||||
bitmap.Unlock(data);
|
||||
for (int x = 0; x < bitmap.Width; x++)
|
||||
{
|
||||
for (int y = 0; y < data.Height; y++)
|
||||
for (int y = 0; y < bitmap.Height; y++)
|
||||
{
|
||||
int r = bytes[stride * y + x * 3];
|
||||
int g = bytes[stride * y + x * 3 + 1];
|
||||
@ -65,7 +70,7 @@ namespace NAPS2.Scan.Images
|
||||
return coverage < coverageThreshold;
|
||||
}
|
||||
|
||||
public bool ExcludePage(Bitmap bitmap, ScanProfile scanProfile)
|
||||
public bool ExcludePage(IMemoryStorage bitmap, ScanProfile scanProfile)
|
||||
{
|
||||
return scanProfile.ExcludeBlankPages && IsBlank(bitmap, scanProfile.BlankPageWhiteThreshold, scanProfile.BlankPageCoverageThreshold);
|
||||
}
|
||||
|
@ -52,87 +52,20 @@ namespace NAPS2.Scan.Images
|
||||
return (size - 832) / 96 + 16;
|
||||
}
|
||||
|
||||
private readonly ScannedImageRenderer scannedImageRenderer;
|
||||
//public Task<Bitmap> RenderThumbnail(ScannedImage scannedImage, int size)
|
||||
//{
|
||||
// using (var snapshot = scannedImage.Preserve())
|
||||
// {
|
||||
// return RenderThumbnail(snapshot, size);
|
||||
// }
|
||||
//}
|
||||
|
||||
public ThumbnailRenderer(ScannedImageRenderer scannedImageRenderer)
|
||||
{
|
||||
this.scannedImageRenderer = scannedImageRenderer;
|
||||
}
|
||||
|
||||
public Task<Bitmap> RenderThumbnail(ScannedImage scannedImage)
|
||||
{
|
||||
return RenderThumbnail(scannedImage, UserConfig.Current.ThumbnailSize);
|
||||
}
|
||||
|
||||
public Task<Bitmap> RenderThumbnail(ScannedImage scannedImage, int size)
|
||||
{
|
||||
using (var snapshot = scannedImage.Preserve())
|
||||
{
|
||||
return RenderThumbnail(snapshot, size);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Bitmap> RenderThumbnail(ScannedImage.Snapshot snapshot, int size)
|
||||
{
|
||||
using (var bitmap = await scannedImageRenderer.Render(snapshot, snapshot.TransformList.Count == 0 ? 0 : size * OVERSAMPLE))
|
||||
{
|
||||
return RenderThumbnail(bitmap, size);
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap RenderThumbnail(Bitmap b)
|
||||
{
|
||||
return RenderThumbnail(b, UserConfig.Current.ThumbnailSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bitmap resized to fit within a thumbnail rectangle, including a border around the picture.
|
||||
/// </summary>
|
||||
/// <param name="b">The bitmap to resize.</param>
|
||||
/// <param name="size">The maximum width and height of the thumbnail.</param>
|
||||
/// <returns>The thumbnail bitmap.</returns>
|
||||
public virtual Bitmap RenderThumbnail(Bitmap b, int size)
|
||||
{
|
||||
var result = new Bitmap(size, size);
|
||||
using (Graphics g = Graphics.FromImage(result))
|
||||
{
|
||||
// The location and dimensions of the old bitmap, scaled and positioned within the thumbnail bitmap
|
||||
int left, top, width, height;
|
||||
|
||||
// We want a nice thumbnail, so use the maximum quality interpolation
|
||||
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
|
||||
if (b.Width > b.Height)
|
||||
{
|
||||
// Fill the new bitmap's width
|
||||
width = size;
|
||||
left = 0;
|
||||
// Scale the drawing height to match the original bitmap's aspect ratio
|
||||
height = (int)(b.Height * (size / (double)b.Width));
|
||||
// Center the drawing vertically
|
||||
top = (size - height) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fill the new bitmap's height
|
||||
height = size;
|
||||
top = 0;
|
||||
// Scale the drawing width to match the original bitmap's aspect ratio
|
||||
width = (int)(b.Width * (size / (double)b.Height));
|
||||
// Center the drawing horizontally
|
||||
left = (size - width) / 2;
|
||||
}
|
||||
|
||||
// Draw the original bitmap onto the new bitmap, using the calculated location and dimensions
|
||||
// Note that there may be some padding if the aspect ratios don't match
|
||||
var destRect = new RectangleF(left, top, width, height);
|
||||
var srcRect = new RectangleF(0, 0, b.Width, b.Height);
|
||||
g.DrawImage(b, destRect, srcRect, GraphicsUnit.Pixel);
|
||||
// Draw a border around the orignal bitmap's content, inside the padding
|
||||
g.DrawRectangle(Pens.Black, left, top, width - 1, height - 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
//public async Task<Bitmap> RenderThumbnail(ScannedImage.Snapshot snapshot, int size)
|
||||
//{
|
||||
// using (var bitmap = await scannedImageRenderer.Render(snapshot, snapshot.TransformList.Count == 0 ? 0 : size * OVERSAMPLE))
|
||||
// {
|
||||
// return RenderThumbnail(bitmap, size);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
@ -11,18 +8,5 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
public class BlackWhiteTransform : Transform
|
||||
{
|
||||
public int Threshold { get; set; }
|
||||
|
||||
public override Bitmap Perform(Bitmap bitmap)
|
||||
{
|
||||
if (bitmap.PixelFormat != PixelFormat.Format24bppRgb && bitmap.PixelFormat != PixelFormat.Format32bppArgb)
|
||||
{
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
var monoBitmap = UnsafeImageOps.ConvertTo1Bpp(bitmap, Threshold);
|
||||
bitmap.Dispose();
|
||||
|
||||
return monoBitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,14 +10,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
public int Brightness { get; set; }
|
||||
|
||||
public override Bitmap Perform(Bitmap bitmap)
|
||||
{
|
||||
float brightnessAdjusted = Brightness / 1000f;
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
UnsafeImageOps.ChangeBrightness(bitmap, brightnessAdjusted);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public override bool IsNull => Brightness == 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
@ -11,32 +9,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
public int Contrast { get; set; }
|
||||
|
||||
public override Bitmap Perform(Bitmap bitmap)
|
||||
{
|
||||
float contrastAdjusted = Contrast / 1000f + 1.0f;
|
||||
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
using (var g = Graphics.FromImage(bitmap))
|
||||
{
|
||||
var attrs = new ImageAttributes();
|
||||
attrs.SetColorMatrix(new ColorMatrix
|
||||
{
|
||||
Matrix00 = contrastAdjusted,
|
||||
Matrix11 = contrastAdjusted,
|
||||
Matrix22 = contrastAdjusted
|
||||
});
|
||||
g.DrawImage(bitmap,
|
||||
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
|
||||
0,
|
||||
0,
|
||||
bitmap.Width,
|
||||
bitmap.Height,
|
||||
GraphicsUnit.Pixel,
|
||||
attrs);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public override bool IsNull => Contrast == 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
@ -18,25 +15,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
public int? OriginalWidth { get; set; }
|
||||
public int? OriginalHeight { get; set; }
|
||||
|
||||
public override Bitmap Perform(Bitmap bitmap)
|
||||
{
|
||||
double xScale = bitmap.Width / (double)(OriginalWidth ?? bitmap.Width),
|
||||
yScale = bitmap.Height / (double)(OriginalHeight ?? bitmap.Height);
|
||||
|
||||
int width = Math.Max(bitmap.Width - (int)Math.Round((Left + Right) * xScale), 1);
|
||||
int height = Math.Max(bitmap.Height - (int)Math.Round((Top + Bottom) * yScale), 1);
|
||||
var result = new Bitmap(width, height, PixelFormat.Format24bppRgb);
|
||||
result.SafeSetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
|
||||
using (var g = Graphics.FromImage(result))
|
||||
{
|
||||
g.Clear(Color.White);
|
||||
g.DrawImage(bitmap, new Rectangle((int)Math.Round(-Left * xScale), (int)Math.Round(-Top * yScale), bitmap.Width, bitmap.Height));
|
||||
}
|
||||
OptimizePixelFormat(bitmap, ref result);
|
||||
bitmap.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool CanSimplify(Transform other) => other is CropTransform other2
|
||||
&& OriginalHeight.HasValue && OriginalWidth.HasValue
|
||||
&& other2.OriginalHeight.HasValue && other2.OriginalWidth.HasValue;
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
@ -11,25 +9,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
public int HueShift { get; set; }
|
||||
|
||||
public override Bitmap Perform(Bitmap bitmap)
|
||||
{
|
||||
if (bitmap.PixelFormat != PixelFormat.Format24bppRgb && bitmap.PixelFormat != PixelFormat.Format32bppArgb)
|
||||
{
|
||||
// No need to handle 1bpp since hue shifts are null transforms
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
float hueShiftAdjusted = HueShift / 2000f * 360;
|
||||
if (hueShiftAdjusted < 0)
|
||||
{
|
||||
hueShiftAdjusted += 360;
|
||||
}
|
||||
|
||||
UnsafeImageOps.HueShift(bitmap, hueShiftAdjusted);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public override bool CanSimplify(Transform other) => other is HueTransform;
|
||||
|
||||
public override Transform Simplify(Transform other)
|
||||
|
@ -2,14 +2,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
[Serializable]
|
||||
public class RotationTransform : Transform
|
||||
{
|
||||
private const double TOLERANCE = 0.001;
|
||||
public const double TOLERANCE = 0.001;
|
||||
|
||||
public static double NormalizeAngle(double angle)
|
||||
{
|
||||
@ -64,51 +63,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
set => angle = NormalizeAngle(value);
|
||||
}
|
||||
|
||||
public override Bitmap Perform(Bitmap bitmap)
|
||||
{
|
||||
if (Math.Abs(Angle - 0.0) < TOLERANCE)
|
||||
{
|
||||
return bitmap;
|
||||
}
|
||||
if (Math.Abs(Angle - 90.0) < TOLERANCE)
|
||||
{
|
||||
bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
|
||||
return bitmap;
|
||||
}
|
||||
if (Math.Abs(Angle - 180.0) < TOLERANCE)
|
||||
{
|
||||
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
|
||||
return bitmap;
|
||||
}
|
||||
if (Math.Abs(Angle - 270.0) < TOLERANCE)
|
||||
{
|
||||
bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
|
||||
return bitmap;
|
||||
}
|
||||
Bitmap result;
|
||||
if (Angle > 45.0 && Angle < 135.0 || Angle > 225.0 && Angle < 315.0)
|
||||
{
|
||||
result = new Bitmap(bitmap.Height, bitmap.Width);
|
||||
result.SafeSetResolution(bitmap.VerticalResolution, bitmap.HorizontalResolution);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new Bitmap(bitmap.Width, bitmap.Height);
|
||||
result.SafeSetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
|
||||
}
|
||||
using (var g = Graphics.FromImage(result))
|
||||
{
|
||||
g.Clear(Color.White);
|
||||
g.TranslateTransform(result.Width / 2.0f, result.Height / 2.0f);
|
||||
g.RotateTransform((float)Angle);
|
||||
g.TranslateTransform(-bitmap.Width / 2.0f, -bitmap.Height / 2.0f);
|
||||
g.DrawImage(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height));
|
||||
}
|
||||
OptimizePixelFormat(bitmap, ref result);
|
||||
bitmap.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool CanSimplify(Transform other) => other is RotationTransform;
|
||||
|
||||
public override Transform Simplify(Transform other)
|
||||
|
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
@ -12,52 +9,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
public int Saturation { get; set; }
|
||||
|
||||
public override Bitmap Perform(Bitmap bitmap)
|
||||
{
|
||||
double saturationAdjusted = Saturation / 1000.0 + 1;
|
||||
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
int bytesPerPixel;
|
||||
if (bitmap.PixelFormat == PixelFormat.Format24bppRgb)
|
||||
{
|
||||
bytesPerPixel = 3;
|
||||
}
|
||||
else if (bitmap.PixelFormat == PixelFormat.Format32bppArgb)
|
||||
{
|
||||
bytesPerPixel = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
|
||||
var stride = Math.Abs(data.Stride);
|
||||
for (int y = 0; y < data.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < data.Width; x++)
|
||||
{
|
||||
int r = Marshal.ReadByte(data.Scan0 + stride * y + x * bytesPerPixel);
|
||||
int g = Marshal.ReadByte(data.Scan0 + stride * y + x * bytesPerPixel + 1);
|
||||
int b = Marshal.ReadByte(data.Scan0 + stride * y + x * bytesPerPixel + 2);
|
||||
|
||||
Color c = Color.FromArgb(255, r, g, b);
|
||||
ColorHelper.ColorToHSL(c, out double h, out double s, out double v);
|
||||
|
||||
s = Math.Min(s * saturationAdjusted, 1);
|
||||
|
||||
c = ColorHelper.ColorFromHSL(h, s, v);
|
||||
|
||||
Marshal.WriteByte(data.Scan0 + stride * y + x * bytesPerPixel, c.R);
|
||||
Marshal.WriteByte(data.Scan0 + stride * y + x * bytesPerPixel + 1, c.G);
|
||||
Marshal.WriteByte(data.Scan0 + stride * y + x * bytesPerPixel + 2, c.B);
|
||||
}
|
||||
}
|
||||
bitmap.UnlockBits(data);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public override bool IsNull => Saturation == 0;
|
||||
}
|
||||
}
|
||||
|
14
NAPS2.Sdk/Scan/Images/Transforms/ScaleTransform.cs
Normal file
14
NAPS2.Sdk/Scan/Images/Transforms/ScaleTransform.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
[Serializable]
|
||||
public class ScaleTransform : Transform
|
||||
{
|
||||
public double ScaleFactor { get; set; }
|
||||
|
||||
public override bool IsNull => ScaleFactor == 1;
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
@ -12,114 +9,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
public int Sharpness { get; set; }
|
||||
|
||||
public override Bitmap Perform(Bitmap bitmap)
|
||||
{
|
||||
double sharpnessAdjusted = Sharpness / 1000.0;
|
||||
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
int bytesPerPixel;
|
||||
if (bitmap.PixelFormat == PixelFormat.Format24bppRgb)
|
||||
{
|
||||
bytesPerPixel = 3;
|
||||
} else if (bitmap.PixelFormat == PixelFormat.Format32bppArgb)
|
||||
{
|
||||
bytesPerPixel = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/17596299
|
||||
|
||||
int width = bitmap.Width;
|
||||
int height = bitmap.Height;
|
||||
|
||||
// Create sharpening filter.
|
||||
const int filterSize = 5;
|
||||
|
||||
var filter = new double[,]
|
||||
{
|
||||
{-1, -1, -1, -1, -1},
|
||||
{-1, 2, 2, 2, -1},
|
||||
{-1, 2, 16, 2, -1},
|
||||
{-1, 2, 2, 2, -1},
|
||||
{-1, -1, -1, -1, -1}
|
||||
};
|
||||
|
||||
double bias = 1.0 - sharpnessAdjusted;
|
||||
double factor = sharpnessAdjusted / 16.0;
|
||||
|
||||
const int s = filterSize / 2;
|
||||
|
||||
var result = new Color[bitmap.Width, bitmap.Height];
|
||||
|
||||
// Lock image bits for read/write.
|
||||
BitmapData pbits = bitmap.LockBits(new Rectangle(0, 0, width, height),
|
||||
ImageLockMode.ReadWrite,
|
||||
bitmap.PixelFormat);
|
||||
|
||||
// Declare an array to hold the bytes of the bitmap.
|
||||
int bytes = pbits.Stride * height;
|
||||
var rgbValues = new byte[bytes];
|
||||
|
||||
// Copy the RGB values into the array.
|
||||
Marshal.Copy(pbits.Scan0, rgbValues, 0, bytes);
|
||||
|
||||
int rgb;
|
||||
// Fill the color array with the new sharpened color values.
|
||||
for (int x = s; x < width - s; x++)
|
||||
{
|
||||
for (int y = s; y < height - s; y++)
|
||||
{
|
||||
double red = 0.0, green = 0.0, blue = 0.0;
|
||||
|
||||
for (int filterX = 0; filterX < filterSize; filterX++)
|
||||
{
|
||||
for (int filterY = 0; filterY < filterSize; filterY++)
|
||||
{
|
||||
int imageX = (x - s + filterX + width) % width;
|
||||
int imageY = (y - s + filterY + height) % height;
|
||||
|
||||
rgb = imageY * pbits.Stride + bytesPerPixel * imageX;
|
||||
|
||||
red += rgbValues[rgb + 2] * filter[filterX, filterY];
|
||||
green += rgbValues[rgb + 1] * filter[filterX, filterY];
|
||||
blue += rgbValues[rgb + 0] * filter[filterX, filterY];
|
||||
}
|
||||
|
||||
rgb = y * pbits.Stride + bytesPerPixel * x;
|
||||
|
||||
int r = Math.Min(Math.Max((int)(factor * red + (bias * rgbValues[rgb + 2])), 0), 255);
|
||||
int g = Math.Min(Math.Max((int)(factor * green + (bias * rgbValues[rgb + 1])), 0), 255);
|
||||
int b = Math.Min(Math.Max((int)(factor * blue + (bias * rgbValues[rgb + 0])), 0), 255);
|
||||
|
||||
result[x, y] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the image with the sharpened pixels.
|
||||
for (int x = s; x < width - s; x++)
|
||||
{
|
||||
for (int y = s; y < height - s; y++)
|
||||
{
|
||||
rgb = y * pbits.Stride + bytesPerPixel * x;
|
||||
|
||||
rgbValues[rgb + 2] = result[x, y].R;
|
||||
rgbValues[rgb + 1] = result[x, y].G;
|
||||
rgbValues[rgb + 0] = result[x, y].B;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the RGB values back to the bitmap.
|
||||
Marshal.Copy(rgbValues, 0, pbits.Scan0, bytes);
|
||||
// Release image bits.
|
||||
bitmap.UnlockBits(pbits);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public override bool IsNull => Sharpness == 0;
|
||||
}
|
||||
}
|
||||
|
13
NAPS2.Sdk/Scan/Images/Transforms/ThumbnailTransform.cs
Normal file
13
NAPS2.Sdk/Scan/Images/Transforms/ThumbnailTransform.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NAPS2.Config;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
[Serializable]
|
||||
public class ThumbnailTransform : Transform
|
||||
{
|
||||
public int Size { get; set; } = UserConfig.Current.ThumbnailSize;
|
||||
}
|
||||
}
|
@ -21,11 +21,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
[Serializable]
|
||||
public abstract class Transform
|
||||
{
|
||||
public static Bitmap PerformAll(Bitmap bitmap, IEnumerable<Transform> transforms)
|
||||
{
|
||||
return transforms.Aggregate(bitmap, (current, t) => t.Perform(current));
|
||||
}
|
||||
|
||||
public static bool AddOrSimplify(IList<Transform> transformList, Transform transform)
|
||||
{
|
||||
if (transform.IsNull)
|
||||
@ -52,50 +47,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the provided bitmap is 1-bit (black and white), replace it with a 24-bit bitmap so that image transforms will work. If the bitmap is replaced, the original is disposed.
|
||||
/// </summary>
|
||||
/// <param name="bitmap">The bitmap that may be replaced.</param>
|
||||
protected static void EnsurePixelFormat(ref Bitmap bitmap)
|
||||
{
|
||||
if (bitmap.PixelFormat == PixelFormat.Format1bppIndexed)
|
||||
{
|
||||
// Copy B&W over to grayscale
|
||||
var bitmap2 = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format24bppRgb);
|
||||
bitmap2.SafeSetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
|
||||
using (var g = Graphics.FromImage(bitmap2))
|
||||
{
|
||||
g.DrawImage(bitmap, 0, 0);
|
||||
}
|
||||
bitmap.Dispose();
|
||||
bitmap = bitmap2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the original bitmap is 1-bit (black and white), optimize the result by making it 1-bit too.
|
||||
/// </summary>
|
||||
/// <param name="original">The original bitmap that is used to determine whether the result should be black and white.</param>
|
||||
/// <param name="result">The result that may be replaced.</param>
|
||||
protected static void OptimizePixelFormat(Bitmap original, ref Bitmap result)
|
||||
{
|
||||
if (original.PixelFormat == PixelFormat.Format1bppIndexed)
|
||||
{
|
||||
var bitmap2 = (Bitmap)BitmapHelper.CopyToBpp(result, 1).Clone();
|
||||
result.Dispose();
|
||||
result = bitmap2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a bitmap with the result of the transform.
|
||||
/// May be the same bitmap object if the transform can be performed in-place.
|
||||
/// The original bitmap is disposed otherwise.
|
||||
/// </summary>
|
||||
/// <param name="bitmap">The bitmap to transform.</param>
|
||||
/// <returns></returns>
|
||||
public abstract Bitmap Perform(Bitmap bitmap);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if this transform performed after another transform can be combined to form a single transform.
|
||||
/// </summary>
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
|
||||
namespace NAPS2.Scan.Images.Transforms
|
||||
@ -10,18 +8,6 @@ namespace NAPS2.Scan.Images.Transforms
|
||||
{
|
||||
public int Contrast { get; set; }
|
||||
|
||||
public override Bitmap Perform(Bitmap bitmap)
|
||||
{
|
||||
// convert +/-1000 input range to a logarithmic scaled multiplier
|
||||
float contrastAdjusted = (float) Math.Pow(2.718281f, Contrast / 500.0f);
|
||||
// see http://docs.rainmeter.net/tips/colormatrix-guide/ for offset & matrix calculation
|
||||
float offset = (1.0f - contrastAdjusted) / 2.0f;
|
||||
|
||||
EnsurePixelFormat(ref bitmap);
|
||||
UnsafeImageOps.ChangeContrast(bitmap, contrastAdjusted, offset);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public override bool IsNull => Contrast == 0;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using ZXing;
|
||||
|
||||
namespace NAPS2.Scan
|
||||
@ -12,10 +13,15 @@ namespace NAPS2.Scan
|
||||
/// </summary>
|
||||
public class PatchCodeDetector
|
||||
{
|
||||
public static PatchCode Detect(Bitmap bitmap)
|
||||
public static PatchCode Detect(IMemoryStorage bitmap)
|
||||
{
|
||||
// TODO: Make more generic
|
||||
if (!(bitmap is GdiStorage gdiStorage))
|
||||
{
|
||||
throw new InvalidOperationException("Patch code detection only supported for GdiStorage");
|
||||
}
|
||||
IBarcodeReader reader = new BarcodeReader();
|
||||
var barcodeResult = reader.Decode(bitmap);
|
||||
var barcodeResult = reader.Decode(gdiStorage.Bitmap);
|
||||
if (barcodeResult != null)
|
||||
{
|
||||
switch (barcodeResult.Text)
|
||||
|
@ -11,6 +11,7 @@ using NAPS2.Logging;
|
||||
using NAPS2.Platform;
|
||||
using NAPS2.Scan.Exceptions;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
using NAPS2.WinForms;
|
||||
@ -253,7 +254,7 @@ namespace NAPS2.Scan.Sane
|
||||
return (null, true);
|
||||
}
|
||||
using (stream)
|
||||
using (var output = Image.FromStream(stream))
|
||||
using (var output = StorageManager.MemoryStorageFactory.Decode(stream, ".bmp"))
|
||||
using (var result = scannedImageHelper.PostProcessStep1(output, ScanProfile, false))
|
||||
{
|
||||
if (blankDetector.ExcludePage(result, ScanProfile))
|
||||
@ -263,7 +264,7 @@ namespace NAPS2.Scan.Sane
|
||||
|
||||
// By converting to 1bpp here we avoid the Win32 call in the BitmapHelper conversion
|
||||
// This converter also has the side effect of working even if the scanner doesn't support Lineart
|
||||
using (var encoded = ScanProfile.BitDepth == ScanBitDepth.BlackWhite ? UnsafeImageOps.ConvertTo1Bpp(result, -ScanProfile.Brightness) : result)
|
||||
using (var encoded = ScanProfile.BitDepth == ScanBitDepth.BlackWhite ? StorageManager.PerformTransform(result, new BlackWhiteTransform { Threshold = -ScanProfile.Brightness }) : result)
|
||||
{
|
||||
var image = new ScannedImage(encoded, ScanProfile.BitDepth, ScanProfile.MaxQuality, ScanProfile.Quality);
|
||||
scannedImageHelper.PostProcessStep2(image, result, ScanProfile, ScanParams, 1, false);
|
||||
|
@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
|
||||
namespace NAPS2.Scan.Stub
|
||||
{
|
||||
@ -74,7 +75,7 @@ namespace NAPS2.Scan.Stub
|
||||
g.FillRectangle(Brushes.LightGray, 0, 0, bitmap.Width, bitmap.Height);
|
||||
g.DrawString((_number++).ToString("G"), new Font("Times New Roman", 80), Brushes.Black, 0, 350);
|
||||
}
|
||||
var image = new ScannedImage(bitmap, ScanBitDepth.C24Bit, ScanProfile.MaxQuality, ScanProfile.Quality);
|
||||
var image = new ScannedImage(new GdiStorage(bitmap), ScanBitDepth.C24Bit, ScanProfile.MaxQuality, ScanProfile.Quality);
|
||||
return image;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using System.Windows.Forms;
|
||||
using NAPS2.Logging;
|
||||
using NAPS2.Scan.Exceptions;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Util;
|
||||
using NAPS2.WinForms;
|
||||
|
||||
@ -129,7 +130,7 @@ namespace NAPS2.Scan.Twain.Legacy
|
||||
|
||||
using (Bitmap bmp = DibUtils.BitmapFromDib(img, out bitcount))
|
||||
{
|
||||
Bitmaps.Add(new ScannedImage(bmp, bitcount == 1 ? ScanBitDepth.BlackWhite : ScanBitDepth.C24Bit, settings.MaxQuality, settings.Quality));
|
||||
Bitmaps.Add(new ScannedImage(new GdiStorage(bmp), bitcount == 1 ? ScanBitDepth.BlackWhite : ScanBitDepth.C24Bit, settings.MaxQuality, settings.Quality));
|
||||
}
|
||||
}
|
||||
form.Close();
|
||||
|
@ -12,6 +12,7 @@ using NAPS2.Logging;
|
||||
using NAPS2.Platform;
|
||||
using NAPS2.Scan.Exceptions;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.WinForms;
|
||||
using NTwain;
|
||||
using NTwain.Data;
|
||||
@ -147,7 +148,7 @@ namespace NAPS2.Scan.Twain
|
||||
pageNumber++;
|
||||
using (var output = twainImpl == TwainImpl.MemXfer
|
||||
? GetBitmapFromMemXFer(eventArgs.MemoryData, eventArgs.ImageInfo)
|
||||
: Image.FromStream(eventArgs.GetNativeImageStream()))
|
||||
: StorageManager.MemoryStorageFactory.Decode(eventArgs.GetNativeImageStream(), ".bmp"))
|
||||
{
|
||||
using (var result = scannedImageHelper.PostProcessStep1(output, scanProfile))
|
||||
{
|
||||
@ -156,7 +157,7 @@ namespace NAPS2.Scan.Twain
|
||||
return;
|
||||
}
|
||||
|
||||
var bitDepth = output.PixelFormat == PixelFormat.Format1bppIndexed
|
||||
var bitDepth = output.PixelFormat == StoragePixelFormat.BW1
|
||||
? ScanBitDepth.BlackWhite
|
||||
: ScanBitDepth.C24Bit;
|
||||
var image = new ScannedImage(result, bitDepth, scanProfile.MaxQuality, scanProfile.Quality);
|
||||
@ -318,21 +319,21 @@ namespace NAPS2.Scan.Twain
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap GetBitmapFromMemXFer(byte[] memoryData, TWImageInfo imageInfo)
|
||||
private static IMemoryStorage GetBitmapFromMemXFer(byte[] memoryData, TWImageInfo imageInfo)
|
||||
{
|
||||
int bytesPerPixel = memoryData.Length / (imageInfo.ImageWidth * imageInfo.ImageLength);
|
||||
PixelFormat pixelFormat = bytesPerPixel == 0 ? PixelFormat.Format1bppIndexed : PixelFormat.Format24bppRgb;
|
||||
var pixelFormat = bytesPerPixel == 0 ? StoragePixelFormat.BW1: StoragePixelFormat.RGB24;
|
||||
int imageWidth = imageInfo.ImageWidth;
|
||||
int imageHeight = imageInfo.ImageLength;
|
||||
var bitmap = new Bitmap(imageWidth, imageHeight, pixelFormat);
|
||||
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
|
||||
var bitmap = StorageManager.MemoryStorageFactory.FromDimensions(imageWidth, imageHeight, pixelFormat);
|
||||
var data = bitmap.Lock(out var scan0, out var stride);
|
||||
try
|
||||
{
|
||||
byte[] source = memoryData;
|
||||
if (bytesPerPixel == 1)
|
||||
{
|
||||
// No 8-bit greyscale format, so we have to transform into 24-bit
|
||||
int rowWidth = data.Stride;
|
||||
int rowWidth = stride;
|
||||
int originalRowWidth = source.Length / imageHeight;
|
||||
byte[] source2 = new byte[rowWidth * imageHeight];
|
||||
for (int row = 0; row < imageHeight; row++)
|
||||
@ -349,7 +350,7 @@ namespace NAPS2.Scan.Twain
|
||||
else if (bytesPerPixel == 3)
|
||||
{
|
||||
// Colors are provided as BGR, they need to be swapped to RGB
|
||||
int rowWidth = data.Stride;
|
||||
int rowWidth = stride;
|
||||
for (int row = 0; row < imageHeight; row++)
|
||||
{
|
||||
for (int col = 0; col < imageWidth; col++)
|
||||
@ -359,11 +360,11 @@ namespace NAPS2.Scan.Twain
|
||||
}
|
||||
}
|
||||
}
|
||||
Marshal.Copy(source, 0, data.Scan0, source.Length);
|
||||
Marshal.Copy(source, 0, scan0, source.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap.UnlockBits(data);
|
||||
bitmap.Unlock(data);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using NAPS2.Logging;
|
||||
using NAPS2.Operation;
|
||||
using NAPS2.Scan.Exceptions;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Wia.Native;
|
||||
using NAPS2.Util;
|
||||
using NAPS2.Worker;
|
||||
@ -137,7 +138,7 @@ namespace NAPS2.Scan.Wia
|
||||
}
|
||||
}
|
||||
|
||||
private void ProduceImage(ScannedImageSource.Concrete source, Image output, ref int pageNumber)
|
||||
private void ProduceImage(ScannedImageSource.Concrete source, IMemoryStorage output, ref int pageNumber)
|
||||
{
|
||||
using (var result = scannedImageHelper.PostProcessStep1(output, ScanProfile))
|
||||
{
|
||||
@ -179,13 +180,13 @@ namespace NAPS2.Scan.Wia
|
||||
foreach (var path in paths)
|
||||
{
|
||||
using (var stream = new FileStream(path, FileMode.Open))
|
||||
using (var output = Image.FromStream(stream))
|
||||
{
|
||||
int frameCount = output.GetFrameCount(FrameDimension.Page);
|
||||
for (int i = 0; i < frameCount; i++)
|
||||
foreach (var storage in StorageManager.MemoryStorageFactory.DecodeMultiple(stream, Path.GetExtension(path), out _))
|
||||
{
|
||||
output.SelectActiveFrame(FrameDimension.Page, i);
|
||||
ProduceImage(source, output, ref pageNumber);
|
||||
using (storage)
|
||||
{
|
||||
ProduceImage(source, storage, ref pageNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -228,9 +229,9 @@ namespace NAPS2.Scan.Wia
|
||||
try
|
||||
{
|
||||
using (args.Stream)
|
||||
using (var output = Image.FromStream(args.Stream))
|
||||
using (var storage = StorageManager.MemoryStorageFactory.Decode(args.Stream, ".bmp"))
|
||||
{
|
||||
ProduceImage(source, output, ref pageNumber);
|
||||
ProduceImage(source, storage, ref pageNumber);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -26,6 +26,7 @@ using NAPS2.Recovery;
|
||||
using NAPS2.Scan;
|
||||
using NAPS2.Scan.Exceptions;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Wia;
|
||||
using NAPS2.Scan.Wia.Native;
|
||||
using NAPS2.Update;
|
||||
@ -1701,7 +1702,7 @@ namespace NAPS2.WinForms
|
||||
}
|
||||
if (includeBitmap)
|
||||
{
|
||||
using (var firstBitmap = await scannedImageRenderer.Render(imageList[0]))
|
||||
using (var firstBitmap = ((GdiStorage) await scannedImageRenderer.Render(imageList[0])).Bitmap)
|
||||
{
|
||||
ido.SetData(DataFormats.Bitmap, true, new Bitmap(firstBitmap));
|
||||
ido.SetData(DataFormats.Rtf, true, await RtfEncodeImages(firstBitmap, imageList));
|
||||
@ -1721,7 +1722,7 @@ namespace NAPS2.WinForms
|
||||
}
|
||||
foreach (var img in images.Skip(1))
|
||||
{
|
||||
using (var bitmap = await scannedImageRenderer.Render(img))
|
||||
using (var bitmap = ((GdiStorage)await scannedImageRenderer.Render(img)).Bitmap)
|
||||
{
|
||||
if (!AppendRtfEncodedImage(bitmap, img.FileFormat, sb, true))
|
||||
{
|
||||
|
@ -11,6 +11,7 @@ using NAPS2.Lang.Resources;
|
||||
using NAPS2.Operation;
|
||||
using NAPS2.Platform;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.WinForms
|
||||
@ -109,7 +110,7 @@ namespace NAPS2.WinForms
|
||||
tiffViewer1.Image?.Dispose();
|
||||
tiffViewer1.Image = null;
|
||||
var newImage = await scannedImageRenderer.Render(ImageList.Images[ImageIndex]);
|
||||
tiffViewer1.Image = newImage;
|
||||
tiffViewer1.Image = ((GdiStorage)newImage).Bitmap;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
@ -4,6 +4,7 @@ using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
using NAPS2.Scan.Images.Transforms;
|
||||
using NAPS2.Util;
|
||||
using Timer = System.Threading.Timer;
|
||||
@ -54,7 +55,8 @@ namespace NAPS2.WinForms
|
||||
{
|
||||
if (!transform.IsNull)
|
||||
{
|
||||
result = transform.Perform(result);
|
||||
// TODO: Maybe the working images etc. should be storage
|
||||
result = ((GdiStorage)StorageManager.PerformTransform(new GdiStorage(result), transform)).Bitmap;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -90,7 +92,7 @@ namespace NAPS2.WinForms
|
||||
Size = new Size(600, 600);
|
||||
|
||||
var maxDimen = Screen.AllScreens.Max(s => Math.Max(s.WorkingArea.Height, s.WorkingArea.Width));
|
||||
workingImage = await scannedImageRenderer.Render(Image, maxDimen * 2);
|
||||
workingImage = ((GdiStorage)await scannedImageRenderer.Render(Image, maxDimen * 2)).Bitmap;
|
||||
if (closed)
|
||||
{
|
||||
workingImage?.Dispose();
|
||||
|
@ -7,6 +7,7 @@ using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using NAPS2.Platform;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
|
||||
namespace NAPS2.WinForms
|
||||
{
|
||||
@ -195,7 +196,7 @@ namespace NAPS2.WinForms
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var thumb = img.GetThumbnail();
|
||||
var thumb = ((GdiStorage)img.GetThumbnail()).Bitmap;
|
||||
if (thumb == null)
|
||||
{
|
||||
return RenderPlaceholder();
|
||||
|
@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using NAPS2.Recovery;
|
||||
using NAPS2.Scan.Images;
|
||||
using NAPS2.Scan.Images.Storage;
|
||||
|
||||
namespace NAPS2.Worker
|
||||
{
|
||||
@ -17,7 +18,7 @@ namespace NAPS2.Worker
|
||||
var scannedImage = new ScannedImage(image);
|
||||
if (thumbnail != null)
|
||||
{
|
||||
scannedImage.SetThumbnail(new Bitmap(new MemoryStream(thumbnail)));
|
||||
scannedImage.SetThumbnail(StorageManager.MemoryStorageFactory.Decode(new MemoryStream(thumbnail), ".bmp"));
|
||||
}
|
||||
ImageCallback?.Invoke(scannedImage, tempImageFilePath);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user