5ea9a814d6
Pdf Import rewritten to greatly improve QR Code detection, reduce reliance on iTextSharp and improve thumbnails. Fixes #50
318 lines
11 KiB
C#
318 lines
11 KiB
C#
using Disco.Data.Repository;
|
|
using PdfiumViewer;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using ZXing;
|
|
using ZXing.Common;
|
|
using ZXing.Multi.QrCode;
|
|
|
|
namespace Disco.Services.Documents.AttachmentImport
|
|
{
|
|
internal class ImportPage : IDisposable
|
|
{
|
|
public DiscoDataContext Database { get; private set; }
|
|
public string SessionId { get; private set; }
|
|
public PdfDocument PdfiumDocument { get; private set; }
|
|
public int PageIndex { get; private set; }
|
|
|
|
public DocumentUniqueIdentifier Identifier { get; private set; }
|
|
|
|
private Result qrCodeResult;
|
|
private float qrCodeResultScale;
|
|
private Image renderedImage;
|
|
private Bitmap renderedThumbnail;
|
|
private RotateFlipType detectedRotation;
|
|
|
|
public ImportPage(DiscoDataContext Database, string SessionId, PdfDocument PdfiumDocument, int PageIndex)
|
|
{
|
|
this.Database = Database;
|
|
this.SessionId = SessionId;
|
|
this.PdfiumDocument = PdfiumDocument;
|
|
this.PageIndex = PageIndex;
|
|
}
|
|
|
|
public bool IsDetected
|
|
{
|
|
get
|
|
{
|
|
return Identifier != null;
|
|
}
|
|
}
|
|
|
|
public Image Image
|
|
{
|
|
get
|
|
{
|
|
return GetRenderedImage();
|
|
}
|
|
}
|
|
|
|
public Bitmap Thumbnail
|
|
{
|
|
get
|
|
{
|
|
return GetRenderedThumbnail();
|
|
}
|
|
}
|
|
|
|
public bool IsValidAttachment
|
|
{
|
|
get
|
|
{
|
|
return Identifier != null &&
|
|
Identifier.Creator != null &&
|
|
Identifier.Target != null &&
|
|
(Identifier.DocumentTemplate != null || Identifier.AttachmentType.HasValue);
|
|
}
|
|
}
|
|
|
|
public int Rotation
|
|
{
|
|
get
|
|
{
|
|
switch (detectedRotation)
|
|
{
|
|
case RotateFlipType.Rotate90FlipNone:
|
|
return 90;
|
|
case RotateFlipType.Rotate180FlipNone:
|
|
return 180;
|
|
case RotateFlipType.Rotate270FlipNone:
|
|
return 270;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteThumbnailSessionCache()
|
|
{
|
|
var sessionCacheLocation = DataStore.CreateLocation(Database, "Cache\\DocumentDropBox_SessionPages");
|
|
var filename = Path.Combine(sessionCacheLocation, $"{SessionId}-{PageIndex + 1}");
|
|
|
|
Thumbnail.SavePng(filename);
|
|
}
|
|
|
|
public void WriteUndetectedImages()
|
|
{
|
|
var undetectedLocation = DataStore.CreateLocation(Database, "DocumentDropBox_Unassigned");
|
|
var filename = Path.Combine(undetectedLocation, $"{SessionId}_{PageIndex + 1}_thumbnail.png");
|
|
Thumbnail.SavePng(filename);
|
|
|
|
using (var largePreview = Image.ResizeImage(700, 700))
|
|
{
|
|
filename = Path.Combine(undetectedLocation, $"{SessionId}_{PageIndex + 1}.jpg");
|
|
largePreview.SaveJpg(90, filename);
|
|
}
|
|
}
|
|
|
|
public bool DetectQRCode()
|
|
{
|
|
var qrReader = new QRCodeMultiReader();
|
|
|
|
var qrReaderHints = new Dictionary<DecodeHintType, object>() {
|
|
{ DecodeHintType.TRY_HARDER, true }
|
|
};
|
|
|
|
var qrImageSource = new BitmapLuminanceSource((Bitmap)Image);
|
|
|
|
var qrBinarizer = new HybridBinarizer(qrImageSource);
|
|
var qrBinaryBitmap = new BinaryBitmap(qrBinarizer);
|
|
|
|
try
|
|
{
|
|
qrCodeResult = qrReader.decodeMultiple(qrBinaryBitmap, qrReaderHints)?.FirstOrDefault();
|
|
qrCodeResultScale = 1F;
|
|
}
|
|
catch (ReaderException)
|
|
{
|
|
// QR Detection Failed
|
|
qrCodeResult = null;
|
|
}
|
|
|
|
if (qrCodeResult == null)
|
|
{
|
|
var sizePoints = PdfiumDocument.PageSizes[PageIndex];
|
|
|
|
// Try at 175%
|
|
using (var image = PdfiumDocument.Render(PageIndex, (int)(sizePoints.Width * 1.75), (int)(sizePoints.Height * 1.75), 72F, 72F, false))
|
|
{
|
|
qrImageSource = new BitmapLuminanceSource((Bitmap)image);
|
|
|
|
// Try Entire Image
|
|
qrBinarizer = new HybridBinarizer(qrImageSource);
|
|
qrBinaryBitmap = new BinaryBitmap(qrBinarizer);
|
|
|
|
try
|
|
{
|
|
qrCodeResult = qrReader.decodeMultiple(qrBinaryBitmap, qrReaderHints)?.FirstOrDefault();
|
|
qrCodeResultScale = 1.75F;
|
|
}
|
|
catch (ReaderException)
|
|
{
|
|
// QR Detection Failed
|
|
qrCodeResult = null;
|
|
}
|
|
}
|
|
|
|
if (qrCodeResult == null)
|
|
{
|
|
// Try at 200%
|
|
using (var image = PdfiumDocument.Render(PageIndex, (int)(sizePoints.Width * 2), (int)(sizePoints.Height * 2), 72F, 72F, false))
|
|
{
|
|
qrImageSource = new BitmapLuminanceSource((Bitmap)image);
|
|
|
|
// Try Entire Image
|
|
qrBinarizer = new HybridBinarizer(qrImageSource);
|
|
qrBinaryBitmap = new BinaryBitmap(qrBinarizer);
|
|
|
|
try
|
|
{
|
|
qrCodeResult = qrReader.decodeMultiple(qrBinaryBitmap, qrReaderHints)?.FirstOrDefault();
|
|
qrCodeResultScale = 2F;
|
|
}
|
|
catch (ReaderException)
|
|
{
|
|
// QR Detection Failed
|
|
qrCodeResult = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (qrCodeResult != null)
|
|
{
|
|
// Detect Rotation
|
|
var rotationAngle = Math.Atan2(
|
|
qrCodeResult.ResultPoints[2].Y - qrCodeResult.ResultPoints[1].Y,
|
|
qrCodeResult.ResultPoints[2].X - qrCodeResult.ResultPoints[1].X) * 180 / Math.PI;
|
|
if (rotationAngle <= 45 || rotationAngle > 315)
|
|
{
|
|
detectedRotation = RotateFlipType.RotateNoneFlipNone;
|
|
}
|
|
else if (rotationAngle <= 135)
|
|
{
|
|
detectedRotation = RotateFlipType.Rotate270FlipNone;
|
|
}
|
|
else if (rotationAngle <= 225)
|
|
{
|
|
detectedRotation = RotateFlipType.Rotate180FlipNone;
|
|
}
|
|
else
|
|
{
|
|
detectedRotation = RotateFlipType.Rotate90FlipNone;
|
|
}
|
|
|
|
// Reset Thumbnail
|
|
if (renderedThumbnail != null)
|
|
{
|
|
renderedThumbnail.Dispose();
|
|
renderedThumbnail = null;
|
|
}
|
|
|
|
// Try binary encoding (from v2)
|
|
if (qrCodeResult.ResultMetadata.ContainsKey(ResultMetadataType.BYTE_SEGMENTS))
|
|
{
|
|
var byteSegments = (List<byte[]>)qrCodeResult.ResultMetadata[ResultMetadataType.BYTE_SEGMENTS];
|
|
var qrBytes = byteSegments[0];
|
|
if (DocumentUniqueIdentifier.IsDocumentUniqueIdentifier(qrBytes))
|
|
{
|
|
Identifier = DocumentUniqueIdentifier.Parse(Database, qrBytes);
|
|
}
|
|
}
|
|
// Fall back to v1
|
|
if (Identifier == null)
|
|
{
|
|
Identifier = DocumentUniqueIdentifier.Parse(Database, qrCodeResult.Text);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public Bitmap GetAttachmentThumbnail()
|
|
{
|
|
var thumbnail = renderedImage.ResizeImage(48, 48, Brushes.White);
|
|
|
|
// Draw Rotation
|
|
if (detectedRotation != RotateFlipType.RotateNoneFlipNone)
|
|
{
|
|
thumbnail.RotateFlip(detectedRotation);
|
|
}
|
|
|
|
// Add PDF Icon overlay
|
|
using (Image mimeTypeIcon = Disco.Services.Properties.Resources.MimeType_pdf16)
|
|
{
|
|
thumbnail.EmbedIconOverlay(mimeTypeIcon);
|
|
}
|
|
|
|
return thumbnail;
|
|
}
|
|
|
|
private Image GetRenderedImage()
|
|
{
|
|
if (renderedImage == null)
|
|
{
|
|
var pageSize = PdfiumDocument.PageSizes[PageIndex];
|
|
|
|
renderedImage = PdfiumDocument.Render(PageIndex, (int)pageSize.Width, (int)pageSize.Height, 72F, 72F, true);
|
|
}
|
|
|
|
return renderedImage;
|
|
}
|
|
|
|
private Bitmap GetRenderedThumbnail()
|
|
{
|
|
if (renderedThumbnail == null)
|
|
{
|
|
renderedThumbnail = GetRenderedImage().ResizeImage(256, 256, Brushes.White);
|
|
|
|
if (qrCodeResult != null && qrCodeResult.ResultPoints.Length == 4)
|
|
{
|
|
float thumbnailScale;
|
|
var thumbnailOffset = renderedImage.CalculateResize(renderedThumbnail.Width, renderedThumbnail.Height, out thumbnailScale);
|
|
thumbnailScale = thumbnailScale / qrCodeResultScale;
|
|
|
|
using (Graphics thumbnailGraphics = Graphics.FromImage(renderedThumbnail))
|
|
{
|
|
// Draw Square on QR Code
|
|
var linePoints = qrCodeResult.ResultPoints.Select(p => new Point((int)(thumbnailOffset.X + (p.X * thumbnailScale)), (int)(thumbnailOffset.Y + (p.Y * thumbnailScale)))).ToList();
|
|
using (GraphicsPath graphicsPath = new GraphicsPath())
|
|
{
|
|
for (int linePointIndex = 0; linePointIndex < (linePoints.Count - 1); linePointIndex++)
|
|
graphicsPath.AddLine(linePoints[linePointIndex], linePoints[linePointIndex + 1]);
|
|
|
|
graphicsPath.AddLine(linePoints[linePoints.Count - 1], linePoints[0]);
|
|
|
|
using (SolidBrush graphicsBrush = new SolidBrush(Color.FromArgb(128, 255, 0, 0)))
|
|
thumbnailGraphics.FillPath(graphicsBrush, graphicsPath);
|
|
|
|
using (Pen graphicsPen = new Pen(Color.FromArgb(200, 255, 0, 0), 2))
|
|
thumbnailGraphics.DrawPath(graphicsPen, graphicsPath);
|
|
}
|
|
|
|
// Draw Rotation
|
|
if (detectedRotation != RotateFlipType.RotateNoneFlipNone)
|
|
{
|
|
renderedThumbnail.RotateFlip(detectedRotation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return renderedThumbnail;
|
|
}
|
|
public void Dispose()
|
|
{
|
|
if (renderedImage != null)
|
|
renderedImage.Dispose();
|
|
if (renderedThumbnail != null)
|
|
renderedThumbnail.Dispose();
|
|
}
|
|
}
|
|
}
|