mirror of
https://github.com/cyanfish/naps2.git
synced 2024-11-13 06:27:11 +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<IOverwritePrompt>().To<ConsoleOverwritePrompt>();
|
||||||
Bind<IOperationProgress>().To<ConsoleOperationProgress>();
|
Bind<IOperationProgress>().To<ConsoleOperationProgress>();
|
||||||
Bind<IComponentInstallPrompt>().To<ConsoleComponentInstallPrompt>();
|
Bind<IComponentInstallPrompt>().To<ConsoleComponentInstallPrompt>();
|
||||||
Bind<ThumbnailRenderer>().To<NullThumbnailRenderer>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NAPS2.Config;
|
using NAPS2.Config;
|
||||||
using NAPS2.ImportExport.Pdf;
|
using NAPS2.ImportExport.Pdf;
|
||||||
using NAPS2.Logging;
|
using NAPS2.Logging;
|
||||||
using NAPS2.Ocr;
|
using NAPS2.Ocr;
|
||||||
using NAPS2.Platform;
|
using NAPS2.Platform;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
namespace NAPS2.DI
|
namespace NAPS2.DI
|
||||||
@ -34,6 +36,11 @@ namespace NAPS2.DI
|
|||||||
|
|
||||||
GhostscriptManager.BasePath = basePath;
|
GhostscriptManager.BasePath = basePath;
|
||||||
OcrManager.Default = new OcrManager(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.Logging;
|
||||||
using NAPS2.Operation;
|
using NAPS2.Operation;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
using NAPS2.Util;
|
using NAPS2.Scan.Images.Storage;
|
||||||
|
using NAPS2.Scan.Images.Transforms;
|
||||||
|
|
||||||
namespace NAPS2.ImportExport
|
namespace NAPS2.ImportExport
|
||||||
{
|
{
|
||||||
public class DirectImportOperation : OperationBase
|
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;
|
AllowCancel = true;
|
||||||
AllowBackground = true;
|
AllowBackground = true;
|
||||||
@ -40,16 +41,16 @@ namespace NAPS2.ImportExport
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
ScannedImage img;
|
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)
|
foreach (var transform in ir.TransformList)
|
||||||
{
|
{
|
||||||
img.AddTransform(transform);
|
img.AddTransform(transform);
|
||||||
}
|
}
|
||||||
// TODO: Don't bother, here, in recovery, etc.
|
// 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);
|
imageCallback(img);
|
||||||
|
|
||||||
Status.CurrentProgress++;
|
Status.CurrentProgress++;
|
||||||
|
@ -8,6 +8,8 @@ using System.Threading.Tasks;
|
|||||||
using NAPS2.Logging;
|
using NAPS2.Logging;
|
||||||
using NAPS2.Scan;
|
using NAPS2.Scan;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
|
using NAPS2.Scan.Images.Transforms;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
|
|
||||||
namespace NAPS2.ImportExport.Images
|
namespace NAPS2.ImportExport.Images
|
||||||
@ -34,10 +36,11 @@ namespace NAPS2.ImportExport.Images
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap toImport;
|
IEnumerable<IMemoryStorage> toImport;
|
||||||
|
int frameCount;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
toImport = new Bitmap(filePath);
|
toImport = StorageManager.MemoryStorageFactory.DecodeMultiple(filePath, out frameCount);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -46,12 +49,11 @@ namespace NAPS2.ImportExport.Images
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (toImport)
|
foreach (var frame in toImport)
|
||||||
|
{
|
||||||
|
using (frame)
|
||||||
{
|
{
|
||||||
int frameCount = toImport.GetFrameCount(FrameDimension.Page);
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var frameIndex in importParams.Slice.Indices(frameCount))
|
|
||||||
{
|
|
||||||
progressCallback(i++, frameCount);
|
progressCallback(i++, frameCount);
|
||||||
if (cancelToken.IsCancellationRequested)
|
if (cancelToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@ -59,15 +61,15 @@ namespace NAPS2.ImportExport.Images
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toImport.SelectActiveFrame(FrameDimension.Page, frameIndex);
|
var image = new ScannedImage(frame, ScanBitDepth.C24Bit, frame.IsOriginalLossless, -1);
|
||||||
var image = new ScannedImage(toImport, ScanBitDepth.C24Bit, IsLossless(toImport.RawFormat), -1);
|
|
||||||
if (!importParams.NoThumbnails)
|
if (!importParams.NoThumbnails)
|
||||||
{
|
{
|
||||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(toImport));
|
image.SetThumbnail(StorageManager.PerformTransform(frame, new ThumbnailTransform()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importParams.DetectPatchCodes)
|
if (importParams.DetectPatchCodes)
|
||||||
{
|
{
|
||||||
image.PatchCode = PatchCodeDetector.Detect(toImport);
|
image.PatchCode = PatchCodeDetector.Detect(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
source.Put(image);
|
source.Put(image);
|
||||||
@ -75,6 +77,7 @@ namespace NAPS2.ImportExport.Images
|
|||||||
|
|
||||||
progressCallback(frameCount, frameCount);
|
progressCallback(frameCount, frameCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
source.Done();
|
source.Done();
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
@ -84,10 +87,5 @@ namespace NAPS2.ImportExport.Images
|
|||||||
}, TaskCreationOptions.LongRunning);
|
}, TaskCreationOptions.LongRunning);
|
||||||
return source;
|
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.Logging;
|
||||||
using NAPS2.Operation;
|
using NAPS2.Operation;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
|
|
||||||
namespace NAPS2.ImportExport.Images
|
namespace NAPS2.ImportExport.Images
|
||||||
@ -173,14 +174,15 @@ namespace NAPS2.ImportExport.Images
|
|||||||
var encoder = ImageCodecInfo.GetImageEncoders().First(x => x.FormatID == ImageFormat.Jpeg.Guid);
|
var encoder = ImageCodecInfo.GetImageEncoders().First(x => x.FormatID == ImageFormat.Jpeg.Guid);
|
||||||
var encoderParams = new EncoderParameters(1);
|
var encoderParams = new EncoderParameters(1);
|
||||||
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, quality);
|
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);
|
bitmap.Save(path, encoder, encoderParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using (Bitmap bitmap = await scannedImageRenderer.Render(snapshot))
|
using (Bitmap bitmap = ((GdiStorage)await scannedImageRenderer.Render(snapshot)).Bitmap)
|
||||||
{
|
{
|
||||||
bitmap.Save(path, format);
|
bitmap.Save(path, format);
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
|
|
||||||
namespace NAPS2.ImportExport.Images
|
namespace NAPS2.ImportExport.Images
|
||||||
@ -39,7 +40,8 @@ namespace NAPS2.ImportExport.Images
|
|||||||
{
|
{
|
||||||
var iparams = new EncoderParameters(1);
|
var iparams = new EncoderParameters(1);
|
||||||
Encoder iparam = Encoder.Compression;
|
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);
|
ValidateBitmap(bitmap);
|
||||||
var iparamPara = new EncoderParameter(iparam, (long)GetEncoderValue(compression, bitmap));
|
var iparamPara = new EncoderParameter(iparam, (long)GetEncoderValue(compression, bitmap));
|
||||||
@ -54,7 +56,7 @@ namespace NAPS2.ImportExport.Images
|
|||||||
var compressionEncoder = Encoder.Compression;
|
var compressionEncoder = Encoder.Compression;
|
||||||
|
|
||||||
File.Delete(location);
|
File.Delete(location);
|
||||||
using (var bitmap0 = await scannedImageRenderer.Render(snapshots[0]))
|
using (var bitmap0 = ((GdiStorage)await scannedImageRenderer.Render(snapshots[0])).Bitmap)
|
||||||
{
|
{
|
||||||
ValidateBitmap(bitmap0);
|
ValidateBitmap(bitmap0);
|
||||||
encoderParams.Param[0] = new EncoderParameter(compressionEncoder, (long)GetEncoderValue(compression, bitmap0));
|
encoderParams.Param[0] = new EncoderParameter(compressionEncoder, (long)GetEncoderValue(compression, bitmap0));
|
||||||
@ -74,7 +76,7 @@ namespace NAPS2.ImportExport.Images
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var bitmap = await scannedImageRenderer.Render(snapshots[i]))
|
using (var bitmap = ((GdiStorage)await scannedImageRenderer.Render(snapshots[i])).Bitmap)
|
||||||
{
|
{
|
||||||
ValidateBitmap(bitmap);
|
ValidateBitmap(bitmap);
|
||||||
encoderParams.Param[0] = new EncoderParameter(compressionEncoder, (long)GetEncoderValue(compression, bitmap));
|
encoderParams.Param[0] = new EncoderParameter(compressionEncoder, (long)GetEncoderValue(compression, bitmap));
|
||||||
|
@ -13,6 +13,8 @@ using NAPS2.Lang.Resources;
|
|||||||
using NAPS2.Logging;
|
using NAPS2.Logging;
|
||||||
using NAPS2.Scan;
|
using NAPS2.Scan;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
|
using NAPS2.Scan.Images.Transforms;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
using PdfSharp.Pdf;
|
using PdfSharp.Pdf;
|
||||||
using PdfSharp.Pdf.Advanced;
|
using PdfSharp.Pdf.Advanced;
|
||||||
@ -179,19 +181,22 @@ namespace NAPS2.ImportExport.Pdf
|
|||||||
|
|
||||||
private async Task<ScannedImage> ExportRawPdfPage(PdfPage page, ImportParams importParams)
|
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();
|
var document = new PdfDocument();
|
||||||
document.Pages.Add(page);
|
document.Pages.Add(page);
|
||||||
document.Save(pdfPath);
|
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)
|
if (!importParams.NoThumbnails || importParams.DetectPatchCodes)
|
||||||
{
|
{
|
||||||
using (var bitmap = await scannedImageRenderer.Render(image))
|
using (var bitmap = await scannedImageRenderer.Render(image))
|
||||||
{
|
{
|
||||||
if (!importParams.NoThumbnails)
|
if (!importParams.NoThumbnails)
|
||||||
{
|
{
|
||||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
image.SetThumbnail(StorageManager.PerformTransform(bitmap, new ThumbnailTransform()));
|
||||||
}
|
}
|
||||||
if (importParams.DetectPatchCodes)
|
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.
|
// 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 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);
|
storage.SetResolution(storage.Width / (float)page.Width.Inch, storage.Height / (float)page.Height.Inch);
|
||||||
var image = new ScannedImage(bitmap, ScanBitDepth.C24Bit, false, -1);
|
var image = new ScannedImage(storage, ScanBitDepth.C24Bit, false, -1);
|
||||||
if (!importParams.NoThumbnails)
|
if (!importParams.NoThumbnails)
|
||||||
{
|
{
|
||||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
image.SetThumbnail(StorageManager.PerformTransform(storage, new ThumbnailTransform()));
|
||||||
}
|
}
|
||||||
if (importParams.DetectPatchCodes)
|
if (importParams.DetectPatchCodes)
|
||||||
{
|
{
|
||||||
image.PatchCode = PatchCodeDetector.Detect(bitmap);
|
image.PatchCode = PatchCodeDetector.Detect(storage);
|
||||||
}
|
}
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
@ -232,51 +237,51 @@ namespace NAPS2.ImportExport.Pdf
|
|||||||
|
|
||||||
var buffer = imageObject.Stream.UnfilteredValue;
|
var buffer = imageObject.Stream.UnfilteredValue;
|
||||||
|
|
||||||
Bitmap bitmap;
|
IMemoryStorage storage;
|
||||||
ScanBitDepth bitDepth;
|
ScanBitDepth bitDepth;
|
||||||
switch (bitsPerComponent)
|
switch (bitsPerComponent)
|
||||||
{
|
{
|
||||||
case 8:
|
case 8:
|
||||||
bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
|
storage = StorageManager.MemoryStorageFactory.FromDimensions(width, height, StoragePixelFormat.RGB24);
|
||||||
bitDepth = ScanBitDepth.C24Bit;
|
bitDepth = ScanBitDepth.C24Bit;
|
||||||
RgbToBitmapUnmanaged(height, width, bitmap, buffer);
|
RgbToBitmapUnmanaged(storage, buffer);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
bitmap = new Bitmap(width, height, PixelFormat.Format1bppIndexed);
|
storage = StorageManager.MemoryStorageFactory.FromDimensions(width, height, StoragePixelFormat.BW1);
|
||||||
bitDepth = ScanBitDepth.BlackWhite;
|
bitDepth = ScanBitDepth.BlackWhite;
|
||||||
BlackAndWhiteToBitmapUnmanaged(height, width, bitmap, buffer);
|
BlackAndWhiteToBitmapUnmanaged(storage, buffer);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException("Unsupported image encoding (expected 24 bpp or 1bpp)");
|
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);
|
storage.SetResolution(storage.Width / (float)page.Width.Inch, storage.Height / (float)page.Height.Inch);
|
||||||
var image = new ScannedImage(bitmap, bitDepth, true, -1);
|
var image = new ScannedImage(storage, bitDepth, true, -1);
|
||||||
if (!importParams.NoThumbnails)
|
if (!importParams.NoThumbnails)
|
||||||
{
|
{
|
||||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
image.SetThumbnail(StorageManager.PerformTransform(storage, new ThumbnailTransform()));
|
||||||
}
|
}
|
||||||
if (importParams.DetectPatchCodes)
|
if (importParams.DetectPatchCodes)
|
||||||
{
|
{
|
||||||
image.PatchCode = PatchCodeDetector.Detect(bitmap);
|
image.PatchCode = PatchCodeDetector.Detect(storage);
|
||||||
}
|
}
|
||||||
return image;
|
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
|
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;
|
IntPtr pixelData = scan0 + y * stride + x * 3;
|
||||||
int bufferIndex = (y * width + x) * 3;
|
int bufferIndex = (y * storage.Width + x) * 3;
|
||||||
Marshal.WriteByte(pixelData, rgbBuffer[bufferIndex + 2]);
|
Marshal.WriteByte(pixelData, rgbBuffer[bufferIndex + 2]);
|
||||||
Marshal.WriteByte(pixelData + 1, rgbBuffer[bufferIndex + 1]);
|
Marshal.WriteByte(pixelData + 1, rgbBuffer[bufferIndex + 1]);
|
||||||
Marshal.WriteByte(pixelData + 2, rgbBuffer[bufferIndex]);
|
Marshal.WriteByte(pixelData + 2, rgbBuffer[bufferIndex]);
|
||||||
@ -285,28 +290,28 @@ namespace NAPS2.ImportExport.Pdf
|
|||||||
}
|
}
|
||||||
finally
|
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
|
try
|
||||||
{
|
{
|
||||||
int bytesPerRow = (width - 1) / 8 + 1;
|
int bytesPerRow = (storage.Width - 1) / 8 + 1;
|
||||||
for (int y = 0; y < height; y++)
|
for (int y = 0; y < storage.Height; y++)
|
||||||
{
|
{
|
||||||
for (int x = 0; x < bytesPerRow; x++)
|
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]);
|
Marshal.WriteByte(pixelData, bwBuffer[y * bytesPerRow + x]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
bitmap.UnlockBits(data);
|
storage.Unlock(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,18 +362,18 @@ namespace NAPS2.ImportExport.Pdf
|
|||||||
Write(stream, TiffTrailer);
|
Write(stream, TiffTrailer);
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
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)
|
if (!importParams.NoThumbnails)
|
||||||
{
|
{
|
||||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
image.SetThumbnail(StorageManager.PerformTransform(storage, new ThumbnailTransform()));
|
||||||
}
|
}
|
||||||
if (importParams.DetectPatchCodes)
|
if (importParams.DetectPatchCodes)
|
||||||
{
|
{
|
||||||
image.PatchCode = PatchCodeDetector.Detect(bitmap);
|
image.PatchCode = PatchCodeDetector.Detect(storage);
|
||||||
}
|
}
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using System.Windows.Forms;
|
|||||||
using NAPS2.Lang.Resources;
|
using NAPS2.Lang.Resources;
|
||||||
using NAPS2.Logging;
|
using NAPS2.Logging;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Scan.Images.Transforms;
|
using NAPS2.Scan.Images.Transforms;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ namespace NAPS2.ImportExport
|
|||||||
if (Math.Sign(image.Width - image.Height) != Math.Sign(pb.Width - pb.Height))
|
if (Math.Sign(image.Width - image.Height) != Math.Sign(pb.Width - pb.Height))
|
||||||
{
|
{
|
||||||
// Flip portrait/landscape to match output
|
// 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
|
// 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, image.Width * pb.Height / image.Height, pb.Height)
|
||||||
: new Rectangle(pb.Left, pb.Top, pb.Width, image.Height * pb.Width / image.Width);
|
: 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
|
finally
|
||||||
{
|
{
|
||||||
|
@ -191,6 +191,11 @@
|
|||||||
<Compile Include="Platform\DefaultRuntimeCompat.cs" />
|
<Compile Include="Platform\DefaultRuntimeCompat.cs" />
|
||||||
<Compile Include="Scan\Exceptions\DriverNotSupportedException.cs" />
|
<Compile Include="Scan\Exceptions\DriverNotSupportedException.cs" />
|
||||||
<Compile Include="Scan\Images\ScannedImageSource.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\PdfFileStorage.cs" />
|
||||||
<Compile Include="Scan\Images\Storage\FileStorageManager.cs" />
|
<Compile Include="Scan\Images\Storage\FileStorageManager.cs" />
|
||||||
<Compile Include="Scan\Images\Storage\GdiFileConverter.cs" />
|
<Compile Include="Scan\Images\Storage\GdiFileConverter.cs" />
|
||||||
@ -201,10 +206,15 @@
|
|||||||
<Compile Include="Scan\Images\Storage\IStorage.cs" />
|
<Compile Include="Scan\Images\Storage\IStorage.cs" />
|
||||||
<Compile Include="Scan\Images\Storage\IStorageConverter.cs" />
|
<Compile Include="Scan\Images\Storage\IStorageConverter.cs" />
|
||||||
<Compile Include="Scan\Images\Storage\IMemoryStorageFactory.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\RecoveryStorageManager.cs" />
|
||||||
<Compile Include="Scan\Images\Storage\StorageConvertParams.cs" />
|
<Compile Include="Scan\Images\Storage\StorageConvertParams.cs" />
|
||||||
<Compile Include="Scan\Images\Storage\StorageManager.cs" />
|
<Compile Include="Scan\Images\Storage\StorageManager.cs" />
|
||||||
<Compile Include="Scan\Images\Storage\StoragePixelFormat.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\KeyValueScanOptions.cs" />
|
||||||
<Compile Include="Scan\Exceptions\SaneNotAvailableException.cs" />
|
<Compile Include="Scan\Exceptions\SaneNotAvailableException.cs" />
|
||||||
<Compile Include="Scan\Sane\SaneOption.cs" />
|
<Compile Include="Scan\Sane\SaneOption.cs" />
|
||||||
@ -345,7 +355,6 @@
|
|||||||
<Compile Include="Scan\Batch\BatchSettings.cs" />
|
<Compile Include="Scan\Batch\BatchSettings.cs" />
|
||||||
<Compile Include="Scan\Exceptions\NoDuplexSupportException.cs" />
|
<Compile Include="Scan\Exceptions\NoDuplexSupportException.cs" />
|
||||||
<Compile Include="Scan\Images\IBlankDetector.cs" />
|
<Compile Include="Scan\Images\IBlankDetector.cs" />
|
||||||
<Compile Include="Scan\Images\NullThumbnailRenderer.cs" />
|
|
||||||
<Compile Include="Scan\Images\ThresholdBlankDetector.cs" />
|
<Compile Include="Scan\Images\ThresholdBlankDetector.cs" />
|
||||||
<Compile Include="Scan\Images\Transforms\BlackWhiteTransform.cs" />
|
<Compile Include="Scan\Images\Transforms\BlackWhiteTransform.cs" />
|
||||||
<Compile Include="Scan\Images\Transforms\ColorHelper.cs" />
|
<Compile Include="Scan\Images\Transforms\ColorHelper.cs" />
|
||||||
@ -470,7 +479,6 @@
|
|||||||
<Compile Include="Scan\Images\ScannedImageHelper.cs" />
|
<Compile Include="Scan\Images\ScannedImageHelper.cs" />
|
||||||
<Compile Include="Scan\Images\ScannedImageList.cs" />
|
<Compile Include="Scan\Images\ScannedImageList.cs" />
|
||||||
<Compile Include="Scan\Images\ThumbnailRenderer.cs" />
|
<Compile Include="Scan\Images\ThumbnailRenderer.cs" />
|
||||||
<Compile Include="Scan\Images\ImageScaleHelper.cs" />
|
|
||||||
<Compile Include="Scan\IScanDriver.cs" />
|
<Compile Include="Scan\IScanDriver.cs" />
|
||||||
<Compile Include="Scan\IScanPerformer.cs" />
|
<Compile Include="Scan\IScanPerformer.cs" />
|
||||||
<Compile Include="Scan\LocalizedDescriptionAttribute.cs" />
|
<Compile Include="Scan\LocalizedDescriptionAttribute.cs" />
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images
|
namespace NAPS2.Scan.Images
|
||||||
{
|
{
|
||||||
public interface IBlankDetector
|
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.Runtime.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NAPS2.Recovery;
|
using NAPS2.Recovery;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Scan.Images.Transforms;
|
using NAPS2.Scan.Images.Transforms;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
|
|
||||||
@ -16,71 +17,86 @@ namespace NAPS2.Scan.Images
|
|||||||
{
|
{
|
||||||
public class ScannedImage : IDisposable
|
public class ScannedImage : IDisposable
|
||||||
{
|
{
|
||||||
// Store the base image and metadata on disk using a separate class to manage lifetime
|
private IMemoryStorage thumbnail;
|
||||||
// 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 int thumbnailState;
|
private int thumbnailState;
|
||||||
private int transformState;
|
private int transformState;
|
||||||
|
|
||||||
private bool disposed;
|
private bool disposed;
|
||||||
private int snapshotCount;
|
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);
|
BackingStorage = StorageManager.ConvertToBacking(storage, convertParams);
|
||||||
|
Metadata = StorageManager.ImageMetadataFactory.CreateMetadata(BackingStorage);
|
||||||
transformList = new List<Transform>();
|
Metadata.Commit();
|
||||||
recoveryImage = RecoveryImage.CreateNew(fileFormat, bitDepth, highQuality, transformList);
|
|
||||||
|
|
||||||
File.Move(tempFilePath, recoveryImage.FilePath);
|
|
||||||
|
|
||||||
recoveryImage.Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScannedImage(RecoveryIndexImage recoveryIndexImage)
|
public ScannedImage(IStorage storage, IImageMetadata metadata, StorageConvertParams convertParams)
|
||||||
{
|
{
|
||||||
recoveryImage = RecoveryImage.LoadExisting(recoveryIndexImage);
|
BackingStorage = StorageManager.ConvertToBacking(storage, convertParams);
|
||||||
transformList = recoveryImage.IndexImage.TransformList;
|
Metadata = metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScannedImage(string pdfPath, bool copy)
|
public ScannedImage(IStorage storage, ScanBitDepth bitDepth, bool highQuality, int quality)
|
||||||
{
|
{
|
||||||
transformList = new List<Transform>();
|
BackingStorage = StorageManager.ConvertToBacking(storage, new StorageConvertParams { Lossless = highQuality, LossyQuality = quality });
|
||||||
recoveryImage = RecoveryImage.CreateNew(null, ScanBitDepth.C24Bit, false, transformList);
|
Metadata = StorageManager.ImageMetadataFactory.CreateMetadata(BackingStorage);
|
||||||
|
// TODO: Is this stuff really needed in metadata?
|
||||||
if (copy)
|
Metadata.BitDepth = bitDepth;
|
||||||
{
|
Metadata.Lossless = highQuality;
|
||||||
File.Copy(pdfPath, recoveryImage.FilePath);
|
Metadata.Commit();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
File.Move(pdfPath, recoveryImage.FilePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recoveryImage.Save();
|
public IStorage BackingStorage { get; }
|
||||||
}
|
|
||||||
|
public IImageMetadata Metadata { get; }
|
||||||
|
|
||||||
public PatchCode PatchCode { get; set; }
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock (this)
|
lock (this)
|
||||||
@ -90,7 +106,7 @@ namespace NAPS2.Scan.Images
|
|||||||
if (snapshotCount != 0) return;
|
if (snapshotCount != 0) return;
|
||||||
|
|
||||||
// Delete the image data on disk
|
// Delete the image data on disk
|
||||||
recoveryImage?.Dispose();
|
BackingStorage?.Dispose();
|
||||||
if (thumbnail != null)
|
if (thumbnail != null)
|
||||||
{
|
{
|
||||||
thumbnail.Dispose();
|
thumbnail.Dispose();
|
||||||
@ -106,13 +122,13 @@ namespace NAPS2.Scan.Images
|
|||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
// Also updates the recovery index since they reference the same list
|
// Also updates the recovery index since they reference the same list
|
||||||
if (!Transform.AddOrSimplify(transformList, transform))
|
if (!Transform.AddOrSimplify(Metadata.TransformList, transform))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
transformState++;
|
transformState++;
|
||||||
}
|
}
|
||||||
recoveryImage.Save();
|
Metadata.Commit();
|
||||||
ThumbnailInvalidated?.Invoke(this, new EventArgs());
|
ThumbnailInvalidated?.Invoke(this, new EventArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,26 +136,26 @@ namespace NAPS2.Scan.Images
|
|||||||
{
|
{
|
||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
if (transformList.Count == 0)
|
if (Metadata.TransformList.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
transformList.Clear();
|
Metadata.TransformList.Clear();
|
||||||
transformState++;
|
transformState++;
|
||||||
}
|
}
|
||||||
recoveryImage.Save();
|
Metadata.Commit();
|
||||||
ThumbnailInvalidated?.Invoke(this, new EventArgs());
|
ThumbnailInvalidated?.Invoke(this, new EventArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap GetThumbnail()
|
public IMemoryStorage GetThumbnail()
|
||||||
{
|
{
|
||||||
lock (this)
|
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)
|
lock (this)
|
||||||
{
|
{
|
||||||
@ -160,7 +176,8 @@ namespace NAPS2.Scan.Images
|
|||||||
|
|
||||||
public void MovedTo(int index)
|
public void MovedTo(int index)
|
||||||
{
|
{
|
||||||
recoveryImage.Move(index);
|
Metadata.Index = index;
|
||||||
|
Metadata.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Snapshot Preserve() => new Snapshot(this);
|
public Snapshot Preserve() => new Snapshot(this);
|
||||||
@ -181,7 +198,7 @@ namespace NAPS2.Scan.Images
|
|||||||
}
|
}
|
||||||
source.snapshotCount++;
|
source.snapshotCount++;
|
||||||
Source = source;
|
Source = source;
|
||||||
TransformList = source.transformList.ToList();
|
TransformList = source.Metadata.TransformList.ToList();
|
||||||
TransformState = source.transformState;
|
TransformState = source.transformState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using NAPS2.Config;
|
using NAPS2.Config;
|
||||||
using NAPS2.Ocr;
|
using NAPS2.Ocr;
|
||||||
using NAPS2.Operation;
|
using NAPS2.Operation;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Scan.Images.Transforms;
|
using NAPS2.Scan.Images.Transforms;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
|
|
||||||
@ -101,29 +102,28 @@ namespace NAPS2.Scan.Images
|
|||||||
return tempFilePath;
|
return tempFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ThumbnailRenderer thumbnailRenderer;
|
|
||||||
private readonly IOperationFactory operationFactory;
|
private readonly IOperationFactory operationFactory;
|
||||||
private readonly IOperationProgress operationProgress;
|
private readonly IOperationProgress operationProgress;
|
||||||
private readonly OcrRequestQueue ocrRequestQueue;
|
private readonly OcrRequestQueue ocrRequestQueue;
|
||||||
private readonly OcrManager ocrManager;
|
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.operationFactory = operationFactory;
|
||||||
this.operationProgress = operationProgress;
|
this.operationProgress = operationProgress;
|
||||||
this.ocrRequestQueue = ocrRequestQueue;
|
this.ocrRequestQueue = ocrRequestQueue;
|
||||||
this.ocrManager = ocrManager;
|
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;
|
double scaleFactor = 1;
|
||||||
if (!profile.UseNativeUI || !supportsNativeUI)
|
if (!profile.UseNativeUI || !supportsNativeUI)
|
||||||
{
|
{
|
||||||
scaleFactor = profile.AfterScanScale.ToIntScaleFactor();
|
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))
|
if ((!profile.UseNativeUI || !supportsNativeUI) && (profile.ForcePageSize || profile.ForcePageSizeCrop))
|
||||||
{
|
{
|
||||||
@ -139,31 +139,31 @@ namespace NAPS2.Scan.Images
|
|||||||
{
|
{
|
||||||
if (profile.ForcePageSizeCrop)
|
if (profile.ForcePageSizeCrop)
|
||||||
{
|
{
|
||||||
result = new CropTransform
|
result = StorageManager.PerformTransform(result, new CropTransform
|
||||||
{
|
{
|
||||||
Right = (int) ((width - (float) pageDimensions.HeightInInches()) * output.HorizontalResolution),
|
Right = (int)((width - (float)pageDimensions.HeightInInches()) * output.HorizontalResolution),
|
||||||
Bottom = (int) ((height - (float) pageDimensions.WidthInInches()) * output.VerticalResolution)
|
Bottom = (int)((height - (float)pageDimensions.WidthInInches()) * output.VerticalResolution)
|
||||||
}.Perform(result);
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result.SafeSetResolution((float) (output.Width / pageDimensions.HeightInInches()),
|
result.SetResolution((float)(output.Width / pageDimensions.HeightInInches()),
|
||||||
(float) (output.Height / pageDimensions.WidthInInches()));
|
(float)(output.Height / pageDimensions.WidthInInches()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (profile.ForcePageSizeCrop)
|
if (profile.ForcePageSizeCrop)
|
||||||
{
|
{
|
||||||
result = new CropTransform
|
result = StorageManager.PerformTransform(result, new CropTransform
|
||||||
{
|
{
|
||||||
Right = (int) ((width - (float) pageDimensions.WidthInInches()) * output.HorizontalResolution),
|
Right = (int)((width - (float)pageDimensions.WidthInInches()) * output.HorizontalResolution),
|
||||||
Bottom = (int) ((height - (float) pageDimensions.HeightInInches()) * output.VerticalResolution)
|
Bottom = (int)((height - (float)pageDimensions.HeightInInches()) * output.VerticalResolution)
|
||||||
}.Perform(result);
|
});
|
||||||
}
|
}
|
||||||
else
|
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;
|
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)
|
if (!scanParams.NoThumbnails)
|
||||||
{
|
{
|
||||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
image.SetThumbnail(StorageManager.PerformTransform(bitmap, new ThumbnailTransform()));
|
||||||
}
|
}
|
||||||
if (scanParams.SkipPostProcessing)
|
if (scanParams.SkipPostProcessing)
|
||||||
{
|
{
|
||||||
@ -220,13 +220,13 @@ namespace NAPS2.Scan.Images
|
|||||||
return scanParams.DoOcr ?? (ocrEnabled && afterScanning);
|
return scanParams.DoOcr ?? (ocrEnabled && afterScanning);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SaveForBackgroundOcr(Bitmap bitmap, ScanParams scanParams)
|
public string SaveForBackgroundOcr(IMemoryStorage bitmap, ScanParams scanParams)
|
||||||
{
|
{
|
||||||
if (ShouldDoBackgroundOcr(scanParams))
|
if (ShouldDoBackgroundOcr(scanParams))
|
||||||
{
|
{
|
||||||
string tempPath = Path.Combine(Paths.Temp, Path.GetRandomFileName());
|
var fileStorage = StorageManager.Convert<FileStorage>(bitmap, new StorageConvertParams { Temporary = true });
|
||||||
bitmap.Save(tempPath);
|
// TODO: Maybe return the storage rather than the path
|
||||||
return tempPath;
|
return fileStorage.FullPath;
|
||||||
}
|
}
|
||||||
return null;
|
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);
|
image.AddTransform(transform);
|
||||||
var thumbnail = image.GetThumbnail();
|
var thumbnail = image.GetThumbnail();
|
||||||
if (thumbnail != null)
|
if (thumbnail != null)
|
||||||
{
|
{
|
||||||
bitmap = transform.Perform(bitmap);
|
bitmap = StorageManager.PerformTransform(bitmap, transform);
|
||||||
image.SetThumbnail(thumbnailRenderer.RenderThumbnail(bitmap));
|
image.SetThumbnail(StorageManager.PerformTransform(bitmap, new ThumbnailTransform()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using System.Drawing;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NAPS2.Recovery;
|
using NAPS2.Recovery;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Scan.Images.Transforms;
|
using NAPS2.Scan.Images.Transforms;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
|
|
||||||
@ -279,7 +280,7 @@ namespace NAPS2.Scan.Images
|
|||||||
var thumb = img.GetThumbnail();
|
var thumb = img.GetThumbnail();
|
||||||
if (thumb != null)
|
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.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NAPS2.ImportExport.Pdf;
|
using NAPS2.ImportExport.Pdf;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Scan.Images.Transforms;
|
using NAPS2.Scan.Images.Transforms;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images
|
namespace NAPS2.Scan.Images
|
||||||
@ -20,7 +21,7 @@ namespace NAPS2.Scan.Images
|
|||||||
this.pdfRenderer = pdfRenderer;
|
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())
|
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(() =>
|
return await Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
var bitmap = snapshot.Source.FileFormat == null
|
var storage = StorageManager.ConvertToMemory(snapshot.Source.BackingStorage, new StorageConvertParams());
|
||||||
? pdfRenderer.Render(snapshot.Source.RecoveryFilePath).Single()
|
|
||||||
: new Bitmap(snapshot.Source.RecoveryFilePath);
|
|
||||||
if (outputSize > 0)
|
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)
|
public async Task<Stream> RenderToStream(ScannedImage image)
|
||||||
{
|
{
|
||||||
using (var snapshot = image.Preserve())
|
using (var snapshot = image.Preserve())
|
||||||
|
@ -5,13 +5,10 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace NAPS2.Scan.Images.Storage
|
namespace NAPS2.Scan.Images.Storage
|
||||||
{
|
{
|
||||||
public class FileStorage : IStorage
|
public class FileStorage : IFileStorage
|
||||||
{
|
{
|
||||||
private readonly FileStorageManager fileStorageManager;
|
public FileStorage(string fullPath)
|
||||||
|
|
||||||
public FileStorage(FileStorageManager fileStorageManager, string fullPath)
|
|
||||||
{
|
{
|
||||||
this.fileStorageManager = fileStorageManager;
|
|
||||||
FullPath = fullPath ?? throw new ArgumentNullException(nameof(fullPath));
|
FullPath = fullPath ?? throw new ArgumentNullException(nameof(fullPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +19,6 @@ namespace NAPS2.Scan.Images.Storage
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.Delete(FullPath);
|
File.Delete(FullPath);
|
||||||
fileStorageManager.Detach(FullPath);
|
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
@ -7,15 +7,14 @@ namespace NAPS2.Scan.Images.Storage
|
|||||||
{
|
{
|
||||||
public class FileStorageManager
|
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 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Storage
|
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;
|
private readonly FileStorageManager fileStorageManager;
|
||||||
|
|
||||||
@ -15,12 +18,21 @@ namespace NAPS2.Scan.Images.Storage
|
|||||||
}
|
}
|
||||||
|
|
||||||
public FileStorage Convert(GdiStorage input, StorageConvertParams convertParams)
|
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
|
// TODO: Save smallest
|
||||||
string ext = convertParams.HighQuality ? ".png" : ".jpg";
|
string ext = convertParams.Lossless ? ".png" : ".jpg";
|
||||||
var path = fileStorageManager.NextFilePath() + ext;
|
var path = fileStorageManager.NextFilePath() + ext;
|
||||||
input.Bitmap.Save(path);
|
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));
|
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 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
|
public StoragePixelFormat PixelFormat
|
||||||
{
|
{
|
||||||
get
|
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)
|
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);
|
var bitmapData = Bitmap.LockBits(new Rectangle(0, 0, Bitmap.Width, Bitmap.Height), ImageLockMode.ReadWrite, Bitmap.PixelFormat);
|
||||||
scan0 = bitmapData.Scan0;
|
scan0 = bitmapData.Scan0;
|
||||||
stride = bitmapData.Stride;
|
stride = Math.Abs(bitmapData.Stride);
|
||||||
return bitmapData;
|
return bitmapData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,5 +69,10 @@ namespace NAPS2.Scan.Images.Storage
|
|||||||
{
|
{
|
||||||
Bitmap.Dispose();
|
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 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)
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Storage
|
namespace NAPS2.Scan.Images.Storage
|
||||||
{
|
{
|
||||||
|
// TODO: Maybe just call this IImage.
|
||||||
public interface IMemoryStorage : IStorage
|
public interface IMemoryStorage : IStorage
|
||||||
{
|
{
|
||||||
int Width { get; }
|
int Width { get; }
|
||||||
|
|
||||||
int Height { get; }
|
int Height { get; }
|
||||||
|
|
||||||
|
float HorizontalResolution { get; }
|
||||||
|
|
||||||
|
float VerticalResolution { get; }
|
||||||
|
|
||||||
|
void SetResolution(float xDpi, float yDpi);
|
||||||
|
|
||||||
StoragePixelFormat PixelFormat { get; }
|
StoragePixelFormat PixelFormat { get; }
|
||||||
|
|
||||||
|
bool IsOriginalLossless { get; }
|
||||||
|
|
||||||
object Lock(out IntPtr scan0, out int stride);
|
object Lock(out IntPtr scan0, out int stride);
|
||||||
|
|
||||||
void Unlock(object state);
|
void Unlock(object state);
|
||||||
|
|
||||||
|
IMemoryStorage Clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,40 @@ namespace NAPS2.Scan.Images.Storage
|
|||||||
{
|
{
|
||||||
public interface IMemoryStorageFactory
|
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
|
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);
|
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
|
namespace NAPS2.Scan.Images.Storage
|
||||||
{
|
{
|
||||||
public class PdfFileStorage : IStorage
|
public class PdfFileStorage : IFileStorage
|
||||||
{
|
{
|
||||||
public PdfFileStorage(string fullPath)
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NAPS2.Config;
|
using NAPS2.Config;
|
||||||
using NAPS2.Recovery;
|
using NAPS2.Recovery;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Storage
|
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;
|
private ConfigManager<RecoveryIndex> indexConfigManager;
|
||||||
|
|
||||||
public RecoveryStorageManager(string recoveryFolderPath)
|
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)
|
get
|
||||||
//{
|
{
|
||||||
// indexConfigManager.Config.Images.Remove(IndexImage);
|
EnsureFolderCreated();
|
||||||
// indexConfigManager.Save();
|
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 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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using NAPS2.Scan.Images.Transforms;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Storage
|
namespace NAPS2.Scan.Images.Storage
|
||||||
@ -18,38 +19,84 @@ namespace NAPS2.Scan.Images.Storage
|
|||||||
|
|
||||||
public static IMemoryStorageFactory MemoryStorageFactory { get; set; } = new GdiStorageFactory();
|
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)>();
|
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")));
|
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()))
|
if (BackingStorageTypes.Contains(storage.GetType()))
|
||||||
{
|
{
|
||||||
return storage;
|
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 storage;
|
||||||
}
|
}
|
||||||
return Convert(storage, PreferredMemoryStorageType);
|
// TODO: Dispose old storage? Consider ownership. Possibility: Clone/Dispose ref counts.
|
||||||
}
|
|
||||||
|
|
||||||
public static IStorage Convert(IStorage storage, Type type)
|
|
||||||
{
|
|
||||||
// TODO: Dispose old storage?
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var (converter, convert) = Converters[(storage.GetType(), type)];
|
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)
|
catch (KeyNotFoundException)
|
||||||
{
|
{
|
||||||
@ -62,6 +109,8 @@ namespace NAPS2.Scan.Images.Storage
|
|||||||
var gdiFileConverter = new GdiFileConverter(new FileStorageManager());
|
var gdiFileConverter = new GdiFileConverter(new FileStorageManager());
|
||||||
RegisterConverter<GdiStorage, FileStorage>(gdiFileConverter);
|
RegisterConverter<GdiStorage, FileStorage>(gdiFileConverter);
|
||||||
RegisterConverter<FileStorage, GdiStorage>(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.Drawing.Imaging;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images
|
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_MIN = 0.00;
|
||||||
private const double COVERAGE_THRESHOLD_MAX = 0.01;
|
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 false;
|
||||||
}
|
}
|
||||||
return IsBlankRGB(bitmap, whiteThresholdNorm, coverageThresholdNorm);
|
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 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);
|
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 totalPixels = bitmap.Width * bitmap.Height;
|
||||||
long matchPixels = 0;
|
long matchPixels = 0;
|
||||||
|
|
||||||
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
|
var data = bitmap.Lock(out var scan0, out var stride);
|
||||||
var stride = Math.Abs(data.Stride);
|
var bytes = new byte[stride * bitmap.Height];
|
||||||
var bytes = new byte[stride * data.Height];
|
Marshal.Copy(scan0, bytes, 0, bytes.Length);
|
||||||
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
|
bitmap.Unlock(data);
|
||||||
bitmap.UnlockBits(data);
|
for (int x = 0; x < bitmap.Width; x++)
|
||||||
for (int x = 0; x < data.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 r = bytes[stride * y + x * 3];
|
||||||
int g = bytes[stride * y + x * 3 + 1];
|
int g = bytes[stride * y + x * 3 + 1];
|
||||||
@ -65,7 +70,7 @@ namespace NAPS2.Scan.Images
|
|||||||
return coverage < coverageThreshold;
|
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);
|
return scanProfile.ExcludeBlankPages && IsBlank(bitmap, scanProfile.BlankPageWhiteThreshold, scanProfile.BlankPageCoverageThreshold);
|
||||||
}
|
}
|
||||||
|
@ -52,87 +52,20 @@ namespace NAPS2.Scan.Images
|
|||||||
return (size - 832) / 96 + 16;
|
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)
|
//public async Task<Bitmap> RenderThumbnail(ScannedImage.Snapshot snapshot, int size)
|
||||||
{
|
//{
|
||||||
this.scannedImageRenderer = scannedImageRenderer;
|
// using (var bitmap = await scannedImageRenderer.Render(snapshot, snapshot.TransformList.Count == 0 ? 0 : size * OVERSAMPLE))
|
||||||
}
|
// {
|
||||||
|
// return RenderThumbnail(bitmap, size);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Transforms
|
namespace NAPS2.Scan.Images.Transforms
|
||||||
{
|
{
|
||||||
@ -11,18 +8,5 @@ namespace NAPS2.Scan.Images.Transforms
|
|||||||
public class BlackWhiteTransform : Transform
|
public class BlackWhiteTransform : Transform
|
||||||
{
|
{
|
||||||
public int Threshold { get; set; }
|
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 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;
|
public override bool IsNull => Brightness == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Transforms
|
namespace NAPS2.Scan.Images.Transforms
|
||||||
@ -11,32 +9,6 @@ namespace NAPS2.Scan.Images.Transforms
|
|||||||
{
|
{
|
||||||
public int Contrast { get; set; }
|
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;
|
public override bool IsNull => Contrast == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NAPS2.Util;
|
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Transforms
|
namespace NAPS2.Scan.Images.Transforms
|
||||||
{
|
{
|
||||||
@ -18,25 +15,6 @@ namespace NAPS2.Scan.Images.Transforms
|
|||||||
public int? OriginalWidth { get; set; }
|
public int? OriginalWidth { get; set; }
|
||||||
public int? OriginalHeight { 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
|
public override bool CanSimplify(Transform other) => other is CropTransform other2
|
||||||
&& OriginalHeight.HasValue && OriginalWidth.HasValue
|
&& OriginalHeight.HasValue && OriginalWidth.HasValue
|
||||||
&& other2.OriginalHeight.HasValue && other2.OriginalWidth.HasValue;
|
&& other2.OriginalHeight.HasValue && other2.OriginalWidth.HasValue;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Transforms
|
namespace NAPS2.Scan.Images.Transforms
|
||||||
@ -11,25 +9,6 @@ namespace NAPS2.Scan.Images.Transforms
|
|||||||
{
|
{
|
||||||
public int HueShift { get; set; }
|
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 bool CanSimplify(Transform other) => other is HueTransform;
|
||||||
|
|
||||||
public override Transform Simplify(Transform other)
|
public override Transform Simplify(Transform other)
|
||||||
|
@ -2,14 +2,13 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NAPS2.Util;
|
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Transforms
|
namespace NAPS2.Scan.Images.Transforms
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class RotationTransform : Transform
|
public class RotationTransform : Transform
|
||||||
{
|
{
|
||||||
private const double TOLERANCE = 0.001;
|
public const double TOLERANCE = 0.001;
|
||||||
|
|
||||||
public static double NormalizeAngle(double angle)
|
public static double NormalizeAngle(double angle)
|
||||||
{
|
{
|
||||||
@ -64,51 +63,6 @@ namespace NAPS2.Scan.Images.Transforms
|
|||||||
set => angle = NormalizeAngle(value);
|
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 bool CanSimplify(Transform other) => other is RotationTransform;
|
||||||
|
|
||||||
public override Transform Simplify(Transform other)
|
public override Transform Simplify(Transform other)
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Transforms
|
namespace NAPS2.Scan.Images.Transforms
|
||||||
{
|
{
|
||||||
@ -12,52 +9,6 @@ namespace NAPS2.Scan.Images.Transforms
|
|||||||
{
|
{
|
||||||
public int Saturation { get; set; }
|
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;
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Transforms
|
namespace NAPS2.Scan.Images.Transforms
|
||||||
{
|
{
|
||||||
@ -12,114 +9,6 @@ namespace NAPS2.Scan.Images.Transforms
|
|||||||
{
|
{
|
||||||
public int Sharpness { get; set; }
|
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;
|
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]
|
[Serializable]
|
||||||
public abstract class Transform
|
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)
|
public static bool AddOrSimplify(IList<Transform> transformList, Transform transform)
|
||||||
{
|
{
|
||||||
if (transform.IsNull)
|
if (transform.IsNull)
|
||||||
@ -52,50 +47,6 @@ namespace NAPS2.Scan.Images.Transforms
|
|||||||
return true;
|
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>
|
/// <summary>
|
||||||
/// Determines if this transform performed after another transform can be combined to form a single transform.
|
/// Determines if this transform performed after another transform can be combined to form a single transform.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Images.Transforms
|
namespace NAPS2.Scan.Images.Transforms
|
||||||
@ -10,18 +8,6 @@ namespace NAPS2.Scan.Images.Transforms
|
|||||||
{
|
{
|
||||||
public int Contrast { get; set; }
|
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;
|
public override bool IsNull => Contrast == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using ZXing;
|
using ZXing;
|
||||||
|
|
||||||
namespace NAPS2.Scan
|
namespace NAPS2.Scan
|
||||||
@ -12,10 +13,15 @@ namespace NAPS2.Scan
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PatchCodeDetector
|
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();
|
IBarcodeReader reader = new BarcodeReader();
|
||||||
var barcodeResult = reader.Decode(bitmap);
|
var barcodeResult = reader.Decode(gdiStorage.Bitmap);
|
||||||
if (barcodeResult != null)
|
if (barcodeResult != null)
|
||||||
{
|
{
|
||||||
switch (barcodeResult.Text)
|
switch (barcodeResult.Text)
|
||||||
|
@ -11,6 +11,7 @@ using NAPS2.Logging;
|
|||||||
using NAPS2.Platform;
|
using NAPS2.Platform;
|
||||||
using NAPS2.Scan.Exceptions;
|
using NAPS2.Scan.Exceptions;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Scan.Images.Transforms;
|
using NAPS2.Scan.Images.Transforms;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
using NAPS2.WinForms;
|
using NAPS2.WinForms;
|
||||||
@ -253,7 +254,7 @@ namespace NAPS2.Scan.Sane
|
|||||||
return (null, true);
|
return (null, true);
|
||||||
}
|
}
|
||||||
using (stream)
|
using (stream)
|
||||||
using (var output = Image.FromStream(stream))
|
using (var output = StorageManager.MemoryStorageFactory.Decode(stream, ".bmp"))
|
||||||
using (var result = scannedImageHelper.PostProcessStep1(output, ScanProfile, false))
|
using (var result = scannedImageHelper.PostProcessStep1(output, ScanProfile, false))
|
||||||
{
|
{
|
||||||
if (blankDetector.ExcludePage(result, ScanProfile))
|
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
|
// 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
|
// 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);
|
var image = new ScannedImage(encoded, ScanProfile.BitDepth, ScanProfile.MaxQuality, ScanProfile.Quality);
|
||||||
scannedImageHelper.PostProcessStep2(image, result, ScanProfile, ScanParams, 1, false);
|
scannedImageHelper.PostProcessStep2(image, result, ScanProfile, ScanParams, 1, false);
|
||||||
|
@ -6,6 +6,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
|
|
||||||
namespace NAPS2.Scan.Stub
|
namespace NAPS2.Scan.Stub
|
||||||
{
|
{
|
||||||
@ -74,7 +75,7 @@ namespace NAPS2.Scan.Stub
|
|||||||
g.FillRectangle(Brushes.LightGray, 0, 0, bitmap.Width, bitmap.Height);
|
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);
|
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;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using System.Windows.Forms;
|
|||||||
using NAPS2.Logging;
|
using NAPS2.Logging;
|
||||||
using NAPS2.Scan.Exceptions;
|
using NAPS2.Scan.Exceptions;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
using NAPS2.WinForms;
|
using NAPS2.WinForms;
|
||||||
|
|
||||||
@ -129,7 +130,7 @@ namespace NAPS2.Scan.Twain.Legacy
|
|||||||
|
|
||||||
using (Bitmap bmp = DibUtils.BitmapFromDib(img, out bitcount))
|
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();
|
form.Close();
|
||||||
|
@ -12,6 +12,7 @@ using NAPS2.Logging;
|
|||||||
using NAPS2.Platform;
|
using NAPS2.Platform;
|
||||||
using NAPS2.Scan.Exceptions;
|
using NAPS2.Scan.Exceptions;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.WinForms;
|
using NAPS2.WinForms;
|
||||||
using NTwain;
|
using NTwain;
|
||||||
using NTwain.Data;
|
using NTwain.Data;
|
||||||
@ -147,7 +148,7 @@ namespace NAPS2.Scan.Twain
|
|||||||
pageNumber++;
|
pageNumber++;
|
||||||
using (var output = twainImpl == TwainImpl.MemXfer
|
using (var output = twainImpl == TwainImpl.MemXfer
|
||||||
? GetBitmapFromMemXFer(eventArgs.MemoryData, eventArgs.ImageInfo)
|
? GetBitmapFromMemXFer(eventArgs.MemoryData, eventArgs.ImageInfo)
|
||||||
: Image.FromStream(eventArgs.GetNativeImageStream()))
|
: StorageManager.MemoryStorageFactory.Decode(eventArgs.GetNativeImageStream(), ".bmp"))
|
||||||
{
|
{
|
||||||
using (var result = scannedImageHelper.PostProcessStep1(output, scanProfile))
|
using (var result = scannedImageHelper.PostProcessStep1(output, scanProfile))
|
||||||
{
|
{
|
||||||
@ -156,7 +157,7 @@ namespace NAPS2.Scan.Twain
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bitDepth = output.PixelFormat == PixelFormat.Format1bppIndexed
|
var bitDepth = output.PixelFormat == StoragePixelFormat.BW1
|
||||||
? ScanBitDepth.BlackWhite
|
? ScanBitDepth.BlackWhite
|
||||||
: ScanBitDepth.C24Bit;
|
: ScanBitDepth.C24Bit;
|
||||||
var image = new ScannedImage(result, bitDepth, scanProfile.MaxQuality, scanProfile.Quality);
|
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);
|
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 imageWidth = imageInfo.ImageWidth;
|
||||||
int imageHeight = imageInfo.ImageLength;
|
int imageHeight = imageInfo.ImageLength;
|
||||||
var bitmap = new Bitmap(imageWidth, imageHeight, pixelFormat);
|
var bitmap = StorageManager.MemoryStorageFactory.FromDimensions(imageWidth, imageHeight, pixelFormat);
|
||||||
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
|
var data = bitmap.Lock(out var scan0, out var stride);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte[] source = memoryData;
|
byte[] source = memoryData;
|
||||||
if (bytesPerPixel == 1)
|
if (bytesPerPixel == 1)
|
||||||
{
|
{
|
||||||
// No 8-bit greyscale format, so we have to transform into 24-bit
|
// 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;
|
int originalRowWidth = source.Length / imageHeight;
|
||||||
byte[] source2 = new byte[rowWidth * imageHeight];
|
byte[] source2 = new byte[rowWidth * imageHeight];
|
||||||
for (int row = 0; row < imageHeight; row++)
|
for (int row = 0; row < imageHeight; row++)
|
||||||
@ -349,7 +350,7 @@ namespace NAPS2.Scan.Twain
|
|||||||
else if (bytesPerPixel == 3)
|
else if (bytesPerPixel == 3)
|
||||||
{
|
{
|
||||||
// Colors are provided as BGR, they need to be swapped to RGB
|
// 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 row = 0; row < imageHeight; row++)
|
||||||
{
|
{
|
||||||
for (int col = 0; col < imageWidth; col++)
|
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
|
finally
|
||||||
{
|
{
|
||||||
bitmap.UnlockBits(data);
|
bitmap.Unlock(data);
|
||||||
}
|
}
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using NAPS2.Logging;
|
|||||||
using NAPS2.Operation;
|
using NAPS2.Operation;
|
||||||
using NAPS2.Scan.Exceptions;
|
using NAPS2.Scan.Exceptions;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Scan.Wia.Native;
|
using NAPS2.Scan.Wia.Native;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
using NAPS2.Worker;
|
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))
|
using (var result = scannedImageHelper.PostProcessStep1(output, ScanProfile))
|
||||||
{
|
{
|
||||||
@ -179,13 +180,13 @@ namespace NAPS2.Scan.Wia
|
|||||||
foreach (var path in paths)
|
foreach (var path in paths)
|
||||||
{
|
{
|
||||||
using (var stream = new FileStream(path, FileMode.Open))
|
using (var stream = new FileStream(path, FileMode.Open))
|
||||||
using (var output = Image.FromStream(stream))
|
|
||||||
{
|
{
|
||||||
int frameCount = output.GetFrameCount(FrameDimension.Page);
|
foreach (var storage in StorageManager.MemoryStorageFactory.DecodeMultiple(stream, Path.GetExtension(path), out _))
|
||||||
for (int i = 0; i < frameCount; i++)
|
|
||||||
{
|
{
|
||||||
output.SelectActiveFrame(FrameDimension.Page, i);
|
using (storage)
|
||||||
ProduceImage(source, output, ref pageNumber);
|
{
|
||||||
|
ProduceImage(source, storage, ref pageNumber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,9 +229,9 @@ namespace NAPS2.Scan.Wia
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (args.Stream)
|
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)
|
catch (Exception e)
|
||||||
|
@ -26,6 +26,7 @@ using NAPS2.Recovery;
|
|||||||
using NAPS2.Scan;
|
using NAPS2.Scan;
|
||||||
using NAPS2.Scan.Exceptions;
|
using NAPS2.Scan.Exceptions;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Scan.Wia;
|
using NAPS2.Scan.Wia;
|
||||||
using NAPS2.Scan.Wia.Native;
|
using NAPS2.Scan.Wia.Native;
|
||||||
using NAPS2.Update;
|
using NAPS2.Update;
|
||||||
@ -1701,7 +1702,7 @@ namespace NAPS2.WinForms
|
|||||||
}
|
}
|
||||||
if (includeBitmap)
|
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.Bitmap, true, new Bitmap(firstBitmap));
|
||||||
ido.SetData(DataFormats.Rtf, true, await RtfEncodeImages(firstBitmap, imageList));
|
ido.SetData(DataFormats.Rtf, true, await RtfEncodeImages(firstBitmap, imageList));
|
||||||
@ -1721,7 +1722,7 @@ namespace NAPS2.WinForms
|
|||||||
}
|
}
|
||||||
foreach (var img in images.Skip(1))
|
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))
|
if (!AppendRtfEncodedImage(bitmap, img.FileFormat, sb, true))
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,7 @@ using NAPS2.Lang.Resources;
|
|||||||
using NAPS2.Operation;
|
using NAPS2.Operation;
|
||||||
using NAPS2.Platform;
|
using NAPS2.Platform;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
|
|
||||||
namespace NAPS2.WinForms
|
namespace NAPS2.WinForms
|
||||||
@ -109,7 +110,7 @@ namespace NAPS2.WinForms
|
|||||||
tiffViewer1.Image?.Dispose();
|
tiffViewer1.Image?.Dispose();
|
||||||
tiffViewer1.Image = null;
|
tiffViewer1.Image = null;
|
||||||
var newImage = await scannedImageRenderer.Render(ImageList.Images[ImageIndex]);
|
var newImage = await scannedImageRenderer.Render(ImageList.Images[ImageIndex]);
|
||||||
tiffViewer1.Image = newImage;
|
tiffViewer1.Image = ((GdiStorage)newImage).Bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
|
@ -4,6 +4,7 @@ using System.Drawing;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
using NAPS2.Scan.Images.Transforms;
|
using NAPS2.Scan.Images.Transforms;
|
||||||
using NAPS2.Util;
|
using NAPS2.Util;
|
||||||
using Timer = System.Threading.Timer;
|
using Timer = System.Threading.Timer;
|
||||||
@ -54,7 +55,8 @@ namespace NAPS2.WinForms
|
|||||||
{
|
{
|
||||||
if (!transform.IsNull)
|
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;
|
return result;
|
||||||
@ -90,7 +92,7 @@ namespace NAPS2.WinForms
|
|||||||
Size = new Size(600, 600);
|
Size = new Size(600, 600);
|
||||||
|
|
||||||
var maxDimen = Screen.AllScreens.Max(s => Math.Max(s.WorkingArea.Height, s.WorkingArea.Width));
|
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)
|
if (closed)
|
||||||
{
|
{
|
||||||
workingImage?.Dispose();
|
workingImage?.Dispose();
|
||||||
|
@ -7,6 +7,7 @@ using System.Reflection;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using NAPS2.Platform;
|
using NAPS2.Platform;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
|
|
||||||
namespace NAPS2.WinForms
|
namespace NAPS2.WinForms
|
||||||
{
|
{
|
||||||
@ -195,7 +196,7 @@ namespace NAPS2.WinForms
|
|||||||
{
|
{
|
||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
var thumb = img.GetThumbnail();
|
var thumb = ((GdiStorage)img.GetThumbnail()).Bitmap;
|
||||||
if (thumb == null)
|
if (thumb == null)
|
||||||
{
|
{
|
||||||
return RenderPlaceholder();
|
return RenderPlaceholder();
|
||||||
|
@ -5,6 +5,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NAPS2.Recovery;
|
using NAPS2.Recovery;
|
||||||
using NAPS2.Scan.Images;
|
using NAPS2.Scan.Images;
|
||||||
|
using NAPS2.Scan.Images.Storage;
|
||||||
|
|
||||||
namespace NAPS2.Worker
|
namespace NAPS2.Worker
|
||||||
{
|
{
|
||||||
@ -17,7 +18,7 @@ namespace NAPS2.Worker
|
|||||||
var scannedImage = new ScannedImage(image);
|
var scannedImage = new ScannedImage(image);
|
||||||
if (thumbnail != null)
|
if (thumbnail != null)
|
||||||
{
|
{
|
||||||
scannedImage.SetThumbnail(new Bitmap(new MemoryStream(thumbnail)));
|
scannedImage.SetThumbnail(StorageManager.MemoryStorageFactory.Decode(new MemoryStream(thumbnail), ".bmp"));
|
||||||
}
|
}
|
||||||
ImageCallback?.Invoke(scannedImage, tempImageFilePath);
|
ImageCallback?.Invoke(scannedImage, tempImageFilePath);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user