From 933c9be910bf4a70262faf45b4c370dbfe9eb71e Mon Sep 17 00:00:00 2001 From: Ben Olden-Cooligan Date: Wed, 29 Jun 2022 21:07:29 -0700 Subject: [PATCH] Implement 24bit twain memory transfer --- .../Internal/Twain/TwainMemoryBufferReader.cs | 118 ++++++++++-------- NAPS2.Sdk/Scan/Internal/TwainScanDriver.cs | 41 ++++-- 2 files changed, 91 insertions(+), 68 deletions(-) diff --git a/NAPS2.Sdk/Scan/Internal/Twain/TwainMemoryBufferReader.cs b/NAPS2.Sdk/Scan/Internal/Twain/TwainMemoryBufferReader.cs index d6080ad85..81e01da6b 100644 --- a/NAPS2.Sdk/Scan/Internal/Twain/TwainMemoryBufferReader.cs +++ b/NAPS2.Sdk/Scan/Internal/Twain/TwainMemoryBufferReader.cs @@ -4,62 +4,70 @@ namespace NAPS2.Scan.Internal.Twain; public class TwainMemoryBufferReader { - private readonly ScanningContext _scanningContext; - - public TwainMemoryBufferReader(ScanningContext scanningContext) + public unsafe void ReadBuffer(TwainMemoryBuffer memoryBuffer, ImagePixelFormat pixelFormat, IMemoryImage outputImage) { - _scanningContext = scanningContext; + var data = outputImage.Lock(LockMode.WriteOnly, out var scan0, out var dstBytesPerRow); + try + { + byte[] source = memoryBuffer.Buffer.ToByteArray(); + if (pixelFormat == ImagePixelFormat.BW1) + { + // TODO: Handle BW1 and also grayscale + // // No 8-bit greyscale format, so we have to transform into 24-bit + // int rowWidth = stride; + // int originalRowWidth = source.Length / imageHeight; + // byte[] source2 = new byte[rowWidth * imageHeight]; + // for (int row = 0; row < imageHeight; row++) + // { + // for (int col = 0; col < imageWidth; col++) + // { + // source2[row * rowWidth + col * 3] = source[row * originalRowWidth + col]; + // source2[row * rowWidth + col * 3 + 1] = source[row * originalRowWidth + col]; + // source2[row * rowWidth + col * 3 + 2] = source[row * originalRowWidth + col]; + // } + // } + // source = source2; + } + else if (pixelFormat == ImagePixelFormat.RGB24) + { + if (memoryBuffer.BytesPerRow < memoryBuffer.Columns * 3 || source.Length < memoryBuffer.BytesPerRow * memoryBuffer.Rows) + { + throw new ArgumentException(); + } + var srcBytesPerRow = memoryBuffer.BytesPerRow; + var bytesPerPixel = 3; + byte* dstPtr = (byte*) scan0.ToPointer(); + fixed (byte* srcPtr = &source[0]) + { + for (int dy = 0; dy < memoryBuffer.Rows; dy++) + { + for (int dx = 0; dx < memoryBuffer.Columns; dx++) + { + int x = memoryBuffer.XOffset + dx; + int y = memoryBuffer.YOffset + dy; + // Colors are provided as BGR, they need to be swapped to RGB + // R + *(dstPtr + y * dstBytesPerRow + x * bytesPerPixel) = + *(srcPtr + dy * srcBytesPerRow + dx * bytesPerPixel + 2); + // G + *(dstPtr + y * dstBytesPerRow + x * bytesPerPixel + 1) = + *(srcPtr + dy * srcBytesPerRow + dx * bytesPerPixel + 1); + // B + *(dstPtr + y * dstBytesPerRow + x * bytesPerPixel + 2) = + *(srcPtr + dy * srcBytesPerRow + dx * bytesPerPixel); + } + } + } + } + else + { + throw new ArgumentException(); + } + } + finally + { + outputImage.Unlock(data); + } } - public IMemoryImage ReadBuffer(MemoryStream buffer, TwainImageData imageData) - { - //Log.Error($"memoryData.Length: {memoryData.Length}, ImageWidth: {imageInfo.ImageWidth}, ImageLength: {imageInfo.ImageLength}, BitsPerPixel: {imageInfo.BitsPerPixel}, SamplesPerPixel: {imageInfo.SamplesPerPixel}, BitsPerSample[0]: {imageInfo.BitsPerSample[0]}, Compression: {imageInfo.Compression}, PixelType: {imageInfo.PixelType}, XRes: {imageInfo.XResolution}, YRes: {imageInfo.YResolution}"); - throw new Exception("blah"); - // int bytesPerPixel = memoryData.Length / (imageInfo.ImageWidth * imageInfo.ImageLength); - // var pixelFormat = bytesPerPixel == 0 ? ImagePixelFormat.BW1 : ImagePixelFormat.RGB24; - // int imageWidth = imageInfo.ImageWidth; - // int imageHeight = imageInfo.ImageLength; - // var bitmap = _scanningContext.ImageContext.Create(imageWidth, imageHeight, pixelFormat); - // var data = bitmap.Lock(LockMode.WriteOnly, out var scan0, out var stride); - // try - // { - // byte[] source = memoryData; - // if (bytesPerPixel == 1) - // { - // // No 8-bit greyscale format, so we have to transform into 24-bit - // int rowWidth = stride; - // int originalRowWidth = source.Length / imageHeight; - // byte[] source2 = new byte[rowWidth * imageHeight]; - // for (int row = 0; row < imageHeight; row++) - // { - // for (int col = 0; col < imageWidth; col++) - // { - // source2[row * rowWidth + col * 3] = source[row * originalRowWidth + col]; - // source2[row * rowWidth + col * 3 + 1] = source[row * originalRowWidth + col]; - // source2[row * rowWidth + col * 3 + 2] = source[row * originalRowWidth + col]; - // } - // } - // source = source2; - // } - // else if (bytesPerPixel == 3) - // { - // // Colors are provided as BGR, they need to be swapped to RGB - // int rowWidth = stride; - // for (int row = 0; row < imageHeight; row++) - // { - // for (int col = 0; col < imageWidth; col++) - // { - // (source[row * rowWidth + col * 3], source[row * rowWidth + col * 3 + 2]) = - // (source[row * rowWidth + col * 3 + 2], source[row * rowWidth + col * 3]); - // } - // } - // } - // Marshal.Copy(source, 0, scan0, source.Length); - // } - // finally - // { - // bitmap.Unlock(data); - // } - // return bitmap; - } } \ No newline at end of file diff --git a/NAPS2.Sdk/Scan/Internal/TwainScanDriver.cs b/NAPS2.Sdk/Scan/Internal/TwainScanDriver.cs index 04da8085e..6dd9829d3 100644 --- a/NAPS2.Sdk/Scan/Internal/TwainScanDriver.cs +++ b/NAPS2.Sdk/Scan/Internal/TwainScanDriver.cs @@ -75,10 +75,11 @@ internal class TwainScanDriver : IScanDriver } } - public async Task Scan(ScanOptions options, CancellationToken cancelToken, IScanEvents scanEvents, Action callback) + public async Task Scan(ScanOptions options, CancellationToken cancelToken, IScanEvents scanEvents, + Action callback) { var controller = GetTwainController(options); - var state = new TwainState(_scanningContext, scanEvents, callback); + using var state = new TwainState(_scanningContext, scanEvents, callback); await controller.StartScan(options, state, cancelToken); } @@ -91,13 +92,13 @@ internal class TwainScanDriver : IScanDriver return new LocalTwainController(); } - private class TwainState : ITwainEvents + private class TwainState : ITwainEvents, IDisposable { private readonly ScanningContext _scanningContext; private readonly IScanEvents _scanEvents; private readonly Action _callback; private TwainImageData? _currentImageData; - private MemoryStream? _currentImageBuffer; + private IMemoryImage? _currentMemoryImage; private long _transferredPixels; private long _totalPixels; @@ -111,7 +112,8 @@ internal class TwainScanDriver : IScanDriver public void PageStart(TwainPageStart pageStart) { _currentImageData = pageStart.ImageData; - _currentImageBuffer = new MemoryStream(); + _currentMemoryImage?.Dispose(); + _currentMemoryImage = null; _transferredPixels = 0; _totalPixels = _currentImageData == null ? 0 : _currentImageData.Width * (long) _currentImageData.Height; _scanEvents.PageStart(); @@ -125,23 +127,36 @@ internal class TwainScanDriver : IScanDriver public void MemoryBufferTransferred(TwainMemoryBuffer memoryBuffer) { - if (_currentImageData == null || _currentImageBuffer == null) + if (_currentImageData == null) { throw new InvalidOperationException(); } + + // TODO: Verify samples etc. + // TODO: Also support grayscale + var pixelFormat = _currentImageData.BitsPerPixel == 1 ? ImagePixelFormat.BW1 : + _currentImageData.BitsPerPixel == 24 ? ImagePixelFormat.RGB24 : + throw new InvalidOperationException($"Unsupported bits per pixel: {_currentImageData.BitsPerPixel}"); + _currentMemoryImage ??= _scanningContext.ImageContext.Create( + _currentImageData.Width, _currentImageData.Height, pixelFormat); + _transferredPixels += memoryBuffer.Columns * (long) memoryBuffer.Rows; _scanEvents.PageProgress(_transferredPixels / (double) _totalPixels); - // TODO: Use Span on netcore? - var buffer = memoryBuffer.Buffer.ToByteArray(); - // TODO: This doesn't handle tiles - _currentImageBuffer.Write(buffer, 0, buffer.Length); + + var bufferReader = new TwainMemoryBufferReader(); + bufferReader.ReadBuffer(memoryBuffer, pixelFormat, _currentMemoryImage); + if (_transferredPixels == _totalPixels) { // TODO: Throw an error if there's a pixel mismatch, i.e. we go to the next page / finish with too few, or have too many - var bufferReader = new TwainMemoryBufferReader(_scanningContext); - var image = bufferReader.ReadBuffer(_currentImageBuffer, _currentImageData); - _callback(image); + _callback(_currentMemoryImage); + _currentMemoryImage = null; } } + + public void Dispose() + { + _currentMemoryImage?.Dispose(); + } } } \ No newline at end of file