Implement 24bit twain memory transfer

This commit is contained in:
Ben Olden-Cooligan 2022-06-29 21:07:29 -07:00
parent 618af42360
commit 933c9be910
2 changed files with 91 additions and 68 deletions

View File

@ -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;
}
}

View File

@ -75,10 +75,11 @@ internal class TwainScanDriver : IScanDriver
}
}
public async Task Scan(ScanOptions options, CancellationToken cancelToken, IScanEvents scanEvents, Action<IMemoryImage> callback)
public async Task Scan(ScanOptions options, CancellationToken cancelToken, IScanEvents scanEvents,
Action<IMemoryImage> 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<IMemoryImage> _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();
}
}
}