From 79085614d34b82730bd24c943b83653b6b8b38af Mon Sep 17 00:00:00 2001 From: Gary Sharp Date: Tue, 12 Mar 2013 15:11:30 +1100 Subject: [PATCH] Update: Document Importing & QR Detection --- .../DocumentTemplateQRCodeLocationCache.cs | 8 +- Disco.BI/BI/Extensions/UtilityExtensions.cs | 48 ++ Disco.BI/BI/Interop/Pdf/PdfImporter.cs | 588 ++++++++++++++++-- Disco.BI/Disco.BI.csproj | 3 +- Disco.BI/Properties/AssemblyInfo.cs | 4 +- Disco.Client/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- Disco.Data/Properties/AssemblyInfo.cs | 4 +- Disco.Models/Properties/AssemblyInfo.cs | 4 +- Disco.Services/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../DocumentTemplate/UndetectedPages.cshtml | 4 +- .../UndetectedPages.generated.cs | 126 ++-- Disco.Web/Properties/AssemblyInfo.cs | 4 +- 14 files changed, 682 insertions(+), 127 deletions(-) diff --git a/Disco.BI/BI/DocumentTemplateBI/DocumentTemplateQRCodeLocationCache.cs b/Disco.BI/BI/DocumentTemplateBI/DocumentTemplateQRCodeLocationCache.cs index e223e818..f128f700 100644 --- a/Disco.BI/BI/DocumentTemplateBI/DocumentTemplateQRCodeLocationCache.cs +++ b/Disco.BI/BI/DocumentTemplateBI/DocumentTemplateQRCodeLocationCache.cs @@ -58,7 +58,13 @@ namespace Disco.BI.DocumentTemplateBI foreach (var pdfFieldPosition in pdfReader.AcroFields.GetFieldPositions("DiscoAttachmentId")) { var pdfPageSize = pdfReader.GetPageSize(pdfFieldPosition.page); - locations.Add(new RectangleF((float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(pdfFieldPosition.position.Left / pdfPageSize.Width) - 0.1)), (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)((pdfPageSize.Height - pdfFieldPosition.position.Top) / pdfPageSize.Height) - 0.1)), (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(pdfFieldPosition.position.Width / pdfPageSize.Width) + 0.2)), (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(pdfFieldPosition.position.Height / pdfPageSize.Height) + 0.2)))); + // Original Position + locations.Add(new RectangleF( + (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(pdfFieldPosition.position.Left / pdfPageSize.Width) - 0.05)), + (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)((pdfPageSize.Height - pdfFieldPosition.position.Top) / pdfPageSize.Height) - 0.05)), + (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(pdfFieldPosition.position.Width / pdfPageSize.Width) + 0.1)), + (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(pdfFieldPosition.position.Height / pdfPageSize.Height) + 0.1)) + )); } } pdfReader.Close(); diff --git a/Disco.BI/BI/Extensions/UtilityExtensions.cs b/Disco.BI/BI/Extensions/UtilityExtensions.cs index 6a5eceb9..c5431614 100644 --- a/Disco.BI/BI/Extensions/UtilityExtensions.cs +++ b/Disco.BI/BI/Extensions/UtilityExtensions.cs @@ -124,6 +124,54 @@ namespace Disco.BI.Extensions #endregion #region Image Extensions + + public static Bitmap RotateImage(this Image Source, float Angle, Brush BackgroundColor = null, bool ResizeIfOver45Deg = true) + { + int destWidth = Source.Width; + int destHeight = Source.Height; + bool resizedDest = false; + + if (ResizeIfOver45Deg && ((Angle > 45 && Angle < 135) || (Angle < -45 && Angle > -135))) + { + destWidth = Source.Height; + destHeight = Source.Width; + resizedDest = true; + } + + Bitmap destination = new Bitmap(destWidth, destHeight); + destination.SetResolution(Source.HorizontalResolution, Source.VerticalResolution); + + using (Graphics destinationGraphics = Graphics.FromImage(destination)) + { + destinationGraphics.CompositingQuality = CompositingQuality.HighQuality; + destinationGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + destinationGraphics.SmoothingMode = SmoothingMode.HighQuality; + + if (BackgroundColor != null) + destinationGraphics.FillRectangle(BackgroundColor, destinationGraphics.VisibleClipBounds); + + float offsetWidth = destWidth / 2; + float offsetHeight = destHeight / 2; + + destinationGraphics.TranslateTransform(offsetWidth, offsetHeight); + destinationGraphics.RotateTransform(Angle); + + RectangleF destinationLocation; + + if (resizedDest) + destinationLocation = new RectangleF( + offsetHeight * -1, offsetWidth * -1, + destHeight, destWidth); + else + destinationLocation = new RectangleF( + offsetWidth * -1, offsetHeight * -1, + destWidth, destHeight); + + destinationGraphics.DrawImage(Source, destinationLocation, new RectangleF(0, 0, Source.Width, Source.Height), GraphicsUnit.Pixel); + } + return destination; + } + public static Bitmap ResizeImage(this Image Source, int Width, int Height, Brush BackgroundColor = null) { Bitmap destination = new Bitmap(Width, Height); diff --git a/Disco.BI/BI/Interop/Pdf/PdfImporter.cs b/Disco.BI/BI/Interop/Pdf/PdfImporter.cs index 28ba6dd6..ca9ea4cd 100644 --- a/Disco.BI/BI/Interop/Pdf/PdfImporter.cs +++ b/Disco.BI/BI/Interop/Pdf/PdfImporter.cs @@ -17,11 +17,31 @@ using Disco.Models.Repository; using System.Collections; using com.google.zxing.common; using BitMiracle.LibTiff.Classic; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; namespace Disco.BI.Interop.Pdf { public static class PdfImporter { + public static RectangleF CalculateLocationRatio(this Result zxingResult, int ImageWidth, int ImageHeight) + { + var orderedPoints = zxingResult.ResultPoints.OrderBy(p => p.X * p.Y).ToArray(); + var topLeftPoint = orderedPoints.First(); + var bottomRightPoint = orderedPoints.Last(); + + var x = topLeftPoint.X; + var y = topLeftPoint.Y; + var width = bottomRightPoint.X - x; + var height = bottomRightPoint.Y - y; + + return new RectangleF( + (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(x / ImageWidth) - 0.05)), + (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(y / ImageHeight) - 0.05)), + (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(width / ImageWidth) + 0.1)), + (float)System.Math.Min(1.0, System.Math.Max(0.0, (double)(height / ImageHeight) + 0.1)) + ); + } private class DetectImageResult : IDisposable { @@ -29,12 +49,46 @@ namespace Disco.BI.Interop.Pdf public Point ResultOffset { get; set; } public double ResultScale { get; set; } + public float CalculateRotation() + { + var p1 = this.Result.ResultPoints[0]; + var p2 = this.Result.ResultPoints[1]; + double rotOpposite = p1.X - p2.X; + double rotAdjacent = p1.Y - p2.Y; + float rotation = 0; + + if (rotOpposite != 0 || rotAdjacent != 0) + { + rotation = (float)(Math.Atan2(rotOpposite, rotAdjacent) * (180 / Math.PI)); // Degrees + } + + return rotation; + } + public void Dispose() { // Do Nothing; yet... } } + private class DetectStateHints + { + public List> PriorDetections { get; set; } + + public DetectStateHints() + { + this.PriorDetections = new List>(); + } + } + + private enum Rotation + { + None = 0, + Degrees90 = 1, + DegreesNeg90 = 2, + Degrees180 = 3 + } + private class DetectPageResult : IDisposable { public int PageNumber { get; set; } @@ -86,7 +140,72 @@ namespace Disco.BI.Interop.Pdf } } - private static DetectImageResult DetectImage(DiscoDataContext dbContext, Bitmap pageImageOriginal, string SessionId, IEnumerable detectDocumentTemplates) + private static Tuple DetectImageFromSegment(Bitmap pageImage, QRCodeMultiReader zxingReader, Hashtable zxingReaderHints, RectangleF LocationPercentage, Rotation Rotation) + { + System.Drawing.Rectangle region; + + switch (Rotation) + { + case Rotation.None: // Original Position + region = new Rectangle( + (int)(pageImage.Width * LocationPercentage.Left), + (int)(pageImage.Height * LocationPercentage.Top), + (int)(pageImage.Width * LocationPercentage.Width), + (int)(pageImage.Height * LocationPercentage.Height)); + break; + case Rotation.Degrees90: // Clockwise 90 degrees + region = new Rectangle( + (int)(pageImage.Width - (pageImage.Width * (LocationPercentage.Top + LocationPercentage.Height))), + (int)(pageImage.Height * LocationPercentage.Left), + (int)(pageImage.Width * LocationPercentage.Height), + (int)(pageImage.Height * LocationPercentage.Width)); + break; + case Rotation.DegreesNeg90: // Anti-clockwise 90 degrees + region = new Rectangle( + (int)(pageImage.Width * LocationPercentage.Top), + (int)(pageImage.Height - (pageImage.Height * (LocationPercentage.Left + LocationPercentage.Width))), + (int)(pageImage.Width * LocationPercentage.Height), + (int)(pageImage.Height * LocationPercentage.Width)); + break; + case Rotation.Degrees180: // 180 degrees + region = new Rectangle( + (int)(pageImage.Width - (pageImage.Width * (LocationPercentage.Left + LocationPercentage.Width))), + (int)(pageImage.Height - (pageImage.Height * (LocationPercentage.Top + LocationPercentage.Height))), + (int)(pageImage.Width * LocationPercentage.Width), + (int)(pageImage.Height * LocationPercentage.Height)); + break; + default: + throw new InvalidOperationException("Unknown Rotation"); + } + + LuminanceSource zxingSource; + using (Bitmap pageImageRegion = new Bitmap(region.Width, region.Height)) + { + pageImageRegion.SetResolution(pageImage.HorizontalResolution, pageImage.VerticalResolution); + + using (Graphics pageImageRegionGraphics = Graphics.FromImage(pageImageRegion)) + { + pageImageRegionGraphics.DrawImage(pageImage, 0, 0, region, GraphicsUnit.Pixel); + } + + zxingSource = new BitmapLuminanceSource(pageImageRegion); + } + var zxingHB = new HybridBinarizer(zxingSource); + var zxingBB = new BinaryBitmap(zxingHB); + try + { + var zxingResult = zxingReader.decode(zxingBB, zxingReaderHints); + if (zxingResult != null) + return new Tuple(zxingResult, region, Rotation); + } + catch (ReaderException) + { + // Ignore Location Errors + } + return null; + } + + private static DetectImageResult DetectImage(DiscoDataContext dbContext, Bitmap pageImageOriginal, string SessionId, IEnumerable detectDocumentTemplates, DetectStateHints StateHints) { Bitmap pageImage = pageImageOriginal; double pageImageModifiedScale = 1; @@ -99,69 +218,77 @@ namespace Disco.BI.Interop.Pdf pageImageModifiedScale = pageImage.HorizontalResolution / 72; int newWidth = (int)((72 / pageImage.HorizontalResolution) * pageImage.Width); int newHeight = (int)((72 / pageImage.VerticalResolution) * pageImage.Height); + pageImage = pageImage.ResizeImage(newWidth, newHeight); } - Result zxingResult = default(Result); - Point zxingResultOffset = Point.Empty; + Tuple result = default(Tuple); QRCodeMultiReader zxingMfr = new QRCodeMultiReader(); Hashtable zxingMfrHints = new Hashtable(); zxingMfrHints.Add(DecodeHintType.TRY_HARDER, true); - // Look in 'Known' locations - foreach (DocumentTemplate dt in detectDocumentTemplates) + + + // Look in previously found locations + if (StateHints.PriorDetections.Count > 0) { - var locationBag = dt.QRCodeLocations(dbContext); - foreach (var location in locationBag) + foreach (var previousLocation in StateHints.PriorDetections) { - System.Drawing.Rectangle region = new Rectangle( - (int)(pageImage.Width * location.Left), - (int)(pageImage.Width * location.Top), - (int)(pageImage.Width * location.Width), - (int)(pageImage.Height * location.Height)); - RGBLuminanceSource zxingSource; - using (Bitmap pageImageRegion = new Bitmap(region.Width, region.Height)) - { - using (Graphics pageImageRegionGraphics = Graphics.FromImage(pageImageRegion)) - { - pageImageRegionGraphics.DrawImage(pageImage, 0, 0, region, GraphicsUnit.Pixel); - } - zxingSource = new RGBLuminanceSource(pageImageRegion, region.Width, region.Height); - } - var zxingHB = new HybridBinarizer(zxingSource); - var zxingBB = new BinaryBitmap(zxingHB); - try - { - zxingResult = zxingMfr.decode(zxingBB, zxingMfrHints); - zxingResultOffset = region.Location; + result = DetectImageFromSegment(pageImage, zxingMfr, zxingMfrHints, + previousLocation.Item1, previousLocation.Item2); + if (result != null) break; - } - catch (ReaderException) - { - // Ignore Location Errors - } } - if (zxingResult != null) - break; } - if (zxingResult == null) + if (result == null) { - // Not found with 'known' locations - // Try whole image - var zxingSource = new RGBLuminanceSource(pageImage, pageImage.Width, pageImage.Height); + // Try the whole image + var zxingSource = new BitmapLuminanceSource(pageImage); var zxingHB = new HybridBinarizer(zxingSource); var zxingBB = new BinaryBitmap(zxingHB); try { - zxingResult = zxingMfr.decode(zxingBB, zxingMfrHints); + var zxingResult = zxingMfr.decode(zxingBB, zxingMfrHints); + if (zxingResult != null) + { + result = new Tuple(zxingResult, new Rectangle(0, 0, pageImage.Width, pageImage.Height), Rotation.None); + + StateHints.PriorDetections.Insert(0, new Tuple( + result.Item1.CalculateLocationRatio(pageImage.Width, pageImage.Height) + , Rotation.None)); + + } } catch (ReaderException) { // Ignore Errors } } + if (result == null) + { + // Look in 'Known' locations + for (int rotationIndex = 0; rotationIndex < 4; rotationIndex++) + { + foreach (DocumentTemplate dt in detectDocumentTemplates) + { + var locationBag = dt.QRCodeLocations(dbContext); + foreach (var location in locationBag) + { + result = DetectImageFromSegment(pageImage, zxingMfr, zxingMfrHints, + location, (Rotation)rotationIndex); - if (zxingResult != null) - return new DetectImageResult() { Result = zxingResult, ResultOffset = zxingResultOffset, ResultScale = pageImageModifiedScale }; + StateHints.PriorDetections.Insert(0, new Tuple(location, (Rotation)rotationIndex)); + } + if (result != null) + break; + } + if (result != null) + break; + } + } + + if (result != null) + + return new DetectImageResult() { Result = result.Item1, ResultOffset = result.Item2.Location, ResultScale = pageImageModifiedScale }; else return null; } @@ -176,7 +303,7 @@ namespace Disco.BI.Interop.Pdf } } - private static DetectPageResult DetectPage(DiscoDataContext dbContext, PdfReader pdfReader, int PageNumber, string SessionId, string DataStoreSessionCacheLocation, IEnumerable detectDocumentTemplates) + private static DetectPageResult DetectPage(DiscoDataContext dbContext, PdfReader pdfReader, int PageNumber, string SessionId, string DataStoreSessionCacheLocation, IEnumerable detectDocumentTemplates, DetectStateHints StateHints) { DetectPageResult result = new DetectPageResult() { PageNumber = PageNumber }; @@ -198,13 +325,23 @@ namespace Disco.BI.Interop.Pdf { DocumentImporterLog.LogImportPageProgress(SessionId, PageNumber, (int)(10 + (pageProgressInterval * pageImages.IndexOf(pageImageOriginal))), String.Format("Processing Page Image {0} of {1}", pageImages.IndexOf(pageImageOriginal) + 1, pageImages.Count)); - using (var zxingResult = DetectImage(dbContext, pageImageOriginal, SessionId, detectDocumentTemplates)) + using (var zxingResult = DetectImage(dbContext, pageImageOriginal, SessionId, detectDocumentTemplates, StateHints)) { if (zxingResult != null) { if (DocumentUniqueIdentifier.IsDocumentUniqueIdentifier(zxingResult.Result.Text)) { + float imageRotation = zxingResult.CalculateRotation(); + result.DrawThumbnailImageResult(zxingResult, pageImageOriginal); + + if (imageRotation != 0) + { + var preImageRotation = result.ThumbnailImage.Montage; + result.ThumbnailImage.Montage = result.ThumbnailImage.Montage.RotateImage(imageRotation); + preImageRotation.Dispose(); + } + result.ThumbnailImage.Montage.SavePng(pageThumbnailFilename); DocumentImporterLog.LogImportPageImageUpdate(SessionId, PageNumber); @@ -213,6 +350,14 @@ namespace Disco.BI.Interop.Pdf { using (Image mimeTypeIcon = Disco.Properties.Resources.MimeType_pdf16) attachmentThumbImage.Montage.EmbedIconOverlay(mimeTypeIcon); + + if (imageRotation != 0) + { + var preImageRotation = attachmentThumbImage.Montage; + attachmentThumbImage.Montage = attachmentThumbImage.Montage.RotateImage(imageRotation, Brushes.White); + preImageRotation.Dispose(); + } + attachmentThumbImage.Montage.SaveJpg(95, result.AttachmentThumbnailImage); } @@ -249,12 +394,14 @@ namespace Disco.BI.Interop.Pdf double progressInterval = 70 / pdfReader.NumberOfPages; + DetectStateHints detectStateHints = new DetectStateHints(); + for (int PageNumber = 1; PageNumber <= pdfReader.NumberOfPages; PageNumber++) { DocumentImporterLog.LogImportProgress(SessionId, (int)(PageNumber * progressInterval), string.Format("Processing Page {0} of {1}", PageNumber, pdfReader.NumberOfPages)); DocumentImporterLog.LogImportPageStarting(SessionId, PageNumber); - using (var pageResult = DetectPage(dbContext, pdfReader, PageNumber, SessionId, dataStoreSessionPagesCacheLocation, detectDocumentTemplates)) + using (var pageResult = DetectPage(dbContext, pdfReader, PageNumber, SessionId, dataStoreSessionPagesCacheLocation, detectDocumentTemplates, detectStateHints)) { if (pageResult.DetectedIdentifier != null) { @@ -468,4 +615,357 @@ namespace Disco.BI.Interop.Pdf } } + + /* +* Copyright 2012 ZXing.Net authors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + /// + /// The base class for luminance sources which supports + /// cropping and rotating based upon the luminance values. + /// + public abstract class BaseLuminanceSource : LuminanceSource + { + /// + /// + /// + protected sbyte[] luminances; + + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The height. + protected BaseLuminanceSource(int width, int height) + : base(width, height) + { + luminances = new sbyte[width * height]; + } + + /// + /// Initializes a new instance of the class. + /// + /// The luminance array. + /// The width. + /// The height. + protected BaseLuminanceSource(sbyte[] luminanceArray, int width, int height) + : base(width, height) + { + luminances = luminanceArray; + //Buffer.BlockCopy(luminanceArray, 0, luminances, 0, width * height); + } + + /// + /// Fetches one row of luminance data from the underlying platform's bitmap. Values range from + /// 0 (black) to 255 (white). It is preferable for implementations of this method + /// to only fetch this row rather than the whole image, since no 2D Readers may be installed and + /// getMatrix() may never be called. + /// + /// The row to fetch, 0 <= y < Height. + /// An optional preallocated array. If null or too small, it will be ignored. + /// Always use the returned object, and ignore the .length of the array. + /// + /// An array containing the luminance data. + /// + override public sbyte[] getRow(int y, sbyte[] row) + { + int width = Width; + if (row == null || row.Length < width) + { + row = new sbyte[width]; + } + //for (int i = 0; i < width; i++) + // row[i] = luminances[y * width + i]; + Buffer.BlockCopy(luminances, y * width, row, 0, width); + return row; + } + + public override sbyte[] Matrix + { + get { return luminances; } + } + + /// + /// Returns a new object with rotated image data by 90 degrees counterclockwise. + /// Only callable if {@link #isRotateSupported()} is true. + /// + /// + /// A rotated version of this object. + /// + public override LuminanceSource rotateCounterClockwise() + { + var rotatedLuminances = new sbyte[Width * Height]; + var newWidth = Height; + var newHeight = Width; + var localLuminances = Matrix; + for (var yold = 0; yold < Height; yold++) + { + for (var xold = 0; xold < Width; xold++) + { + var ynew = xold; + var xnew = newWidth - yold - 1; + rotatedLuminances[ynew * newWidth + xnew] = localLuminances[yold * Width + xold]; + } + } + return CreateLuminanceSource(rotatedLuminances, newWidth, newHeight); + } + + /// + /// + /// Whether this subclass supports counter-clockwise rotation. + public override bool RotateSupported + { + get + { + return true; + } + } + + /// + /// Returns a new object with cropped image data. Implementations may keep a reference to the + /// original data rather than a copy. Only callable if CropSupported is true. + /// + /// The left coordinate, 0 <= left < Width. + /// The top coordinate, 0 <= top <= Height. + /// The width of the rectangle to crop. + /// The height of the rectangle to crop. + /// + /// A cropped version of this object. + /// + public override LuminanceSource crop(int left, int top, int width, int height) + { + if (left + width > Width || top + height > Height) + { + throw new ArgumentException("Crop rectangle does not fit within image data."); + } + var croppedLuminances = new sbyte[width * height]; + for (int yold = top, ynew = 0; yold < height; yold++, ynew++) + { + for (int xold = left, xnew = 0; xold < width; xold++, xnew++) + { + croppedLuminances[ynew * width + xnew] = luminances[yold * Width + xold]; + } + } + return CreateLuminanceSource(croppedLuminances, width, height); + } + + /// + /// + /// Whether this subclass supports cropping. + public override bool CropSupported + { + get + { + return true; + } + } + + /// + /// Should create a new luminance source with the right class type. + /// The method is used in methods crop and rotate. + /// + /// The new luminances. + /// The width. + /// The height. + /// + protected abstract LuminanceSource CreateLuminanceSource(sbyte[] newLuminances, int width, int height); + } + public partial class BitmapLuminanceSource : BaseLuminanceSource + { + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The height. + protected BitmapLuminanceSource(int width, int height) + : base(width, height) + { + } + + /// + /// Initializes a new instance of the class + /// with the image of a Bitmap instance + /// + /// The bitmap. + public BitmapLuminanceSource(Bitmap bitmap) + : base(bitmap.Width, bitmap.Height) + { + var height = bitmap.Height; + var width = bitmap.Width; + + // In order to measure pure decoding speed, we convert the entire image to a greyscale array + luminances = new sbyte[width * height]; + + // The underlying raster of image consists of bytes with the luminance values +#if WindowsCE + var data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); +#else + var data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bitmap.PixelFormat); +#endif + try + { + var stride = Math.Abs(data.Stride); + var pixelWidth = stride / width; + + if (pixelWidth > 4) + { + // old slow way for unsupported bit depth + Color c; + for (int y = 0; y < height; y++) + { + int offset = y * width; + for (int x = 0; x < width; x++) + { + c = bitmap.GetPixel(x, y); + luminances[offset + x] = (sbyte)(0.3 * c.R + 0.59 * c.G + 0.11 * c.B + 0.01); + } + } + } + else + { + var strideStep = data.Stride; + var buffer = new byte[stride]; + var ptrInBitmap = data.Scan0; + +#if !WindowsCE + // prepare palette for 1 and 8 bit indexed bitmaps + var luminancePalette = new sbyte[bitmap.Palette.Entries.Length]; + for (var index = 0; index < bitmap.Palette.Entries.Length; index++) + { + var color = bitmap.Palette.Entries[index]; + luminancePalette[index] = (sbyte)(0.3 * color.R + + 0.59 * color.G + + 0.11 * color.B + 0.01); + } + if (bitmap.PixelFormat == PixelFormat.Format32bppArgb || + bitmap.PixelFormat == PixelFormat.Format32bppPArgb) + { + pixelWidth = 40; + } +#endif + + for (int y = 0; y < height; y++) + { + // copy a scanline not the whole bitmap because of memory usage + Marshal.Copy(ptrInBitmap, buffer, 0, stride); +#if NET40 + ptrInBitmap = IntPtr.Add(ptrInBitmap, strideStep); +#else + ptrInBitmap = new IntPtr(ptrInBitmap.ToInt64() + strideStep); +#endif + var offset = y * width; + switch (pixelWidth) + { +#if !WindowsCE + case 0: + for (int x = 0; x * 8 < width; x++) + { + for (int subX = 0; subX < 8 && 8 * x + subX < width; subX++) + { + var index = (buffer[x] >> (7 - subX)) & 1; + luminances[offset + 8 * x + subX] = luminancePalette[index]; + } + } + break; + case 1: + for (int x = 0; x < width; x++) + { + luminances[offset + x] = luminancePalette[buffer[x]]; + } + break; +#endif + case 2: + // should be RGB565 or RGB555, assume RGB565 + { + for (int index = 0, x = 0; index < 2 * width; index += 2, x++) + { + var byte1 = buffer[index]; + var byte2 = buffer[index + 1]; + + var b5 = byte1 & 0x1F; + var g5 = (((byte1 & 0xE0) >> 5) | ((byte2 & 0x03) << 3)) & 0x1F; + var r5 = (byte2 >> 2) & 0x1F; + var r8 = (r5 * 527 + 23) >> 6; + var g8 = (g5 * 527 + 23) >> 6; + var b8 = (b5 * 527 + 23) >> 6; + + luminances[offset + x] = (sbyte)(0.3 * r8 + 0.59 * g8 + 0.11 * b8 + 0.01); + } + } + break; + case 3: + for (int x = 0; x < width; x++) + { + var luminance = (sbyte)(0.3 * buffer[x * 3] + + 0.59 * buffer[x * 3 + 1] + + 0.11 * buffer[x * 3 + 2] + 0.01); + luminances[offset + x] = luminance; + } + break; + case 4: + // 4 bytes without alpha channel value + for (int x = 0; x < width; x++) + { + var luminance = (sbyte)(0.30 * buffer[x * 4] + + 0.59 * buffer[x * 4 + 1] + + 0.11 * buffer[x * 4 + 2] + 0.01); + + luminances[offset + x] = luminance; + } + break; + case 40: + // with alpha channel; some barcodes are completely black if you + // only look at the r, g and b channel but the alpha channel controls + // the view + for (int x = 0; x < width; x++) + { + var luminance = (sbyte)(0.30 * buffer[x * 4] + + 0.59 * buffer[x * 4 + 1] + + 0.11 * buffer[x * 4 + 2] + 0.01); + + // calculating the resulting luminance based upon a white background + // var alpha = buffer[x * pixelWidth + 3] / 255.0; + // luminance = (byte)(luminance * alpha + 255 * (1 - alpha)); + var alpha = buffer[x * 4 + 3]; + luminance = (sbyte)(((luminance * alpha) >> 8) + (255 * (255 - alpha) >> 8)); + luminances[offset + x] = luminance; + } + break; + default: + throw new NotSupportedException(); + } + } + } + } + finally + { + bitmap.UnlockBits(data); + } + } + + /// + /// Should create a new luminance source with the right class type. + /// The method is used in methods crop and rotate. + /// + /// The new luminances. + /// The width. + /// The height. + /// + protected override LuminanceSource CreateLuminanceSource(sbyte[] newLuminances, int width, int height) + { + return new BitmapLuminanceSource(width, height) { luminances = newLuminances }; + } + } + } diff --git a/Disco.BI/Disco.BI.csproj b/Disco.BI/Disco.BI.csproj index bfd6847d..e25dd11a 100644 --- a/Disco.BI/Disco.BI.csproj +++ b/Disco.BI/Disco.BI.csproj @@ -25,6 +25,7 @@ prompt 4 false + true pdbonly @@ -219,7 +220,7 @@ - + diff --git a/Disco.BI/Properties/AssemblyInfo.cs b/Disco.BI/Properties/AssemblyInfo.cs index 91e9acbb..f75f11da 100644 --- a/Disco.BI/Properties/AssemblyInfo.cs +++ b/Disco.BI/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0304.2046")] -[assembly: AssemblyFileVersion("1.2.0304.2046")] +[assembly: AssemblyVersion("1.2.0307.1501")] +[assembly: AssemblyFileVersion("1.2.0307.1501")] diff --git a/Disco.Client/Properties/AssemblyInfo.cs b/Disco.Client/Properties/AssemblyInfo.cs index 90152eec..987fbaa9 100644 --- a/Disco.Client/Properties/AssemblyInfo.cs +++ b/Disco.Client/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0304.2046")] -[assembly: AssemblyFileVersion("1.2.0304.2046")] +[assembly: AssemblyVersion("1.2.0307.1501")] +[assembly: AssemblyFileVersion("1.2.0307.1501")] diff --git a/Disco.ClientBootstrapper/Properties/AssemblyInfo.cs b/Disco.ClientBootstrapper/Properties/AssemblyInfo.cs index b2eccb5c..d38aa10b 100644 --- a/Disco.ClientBootstrapper/Properties/AssemblyInfo.cs +++ b/Disco.ClientBootstrapper/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0304.2046")] -[assembly: AssemblyFileVersion("1.2.0304.2046")] +[assembly: AssemblyVersion("1.2.0307.1501")] +[assembly: AssemblyFileVersion("1.2.0307.1501")] diff --git a/Disco.Data/Properties/AssemblyInfo.cs b/Disco.Data/Properties/AssemblyInfo.cs index 9e7f7ae3..911a0f4b 100644 --- a/Disco.Data/Properties/AssemblyInfo.cs +++ b/Disco.Data/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0304.2046")] -[assembly: AssemblyFileVersion("1.2.0304.2046")] +[assembly: AssemblyVersion("1.2.0307.1501")] +[assembly: AssemblyFileVersion("1.2.0307.1501")] diff --git a/Disco.Models/Properties/AssemblyInfo.cs b/Disco.Models/Properties/AssemblyInfo.cs index f544827d..4840b225 100644 --- a/Disco.Models/Properties/AssemblyInfo.cs +++ b/Disco.Models/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0304.2046")] -[assembly: AssemblyFileVersion("1.2.0304.2046")] +[assembly: AssemblyVersion("1.2.0307.1501")] +[assembly: AssemblyFileVersion("1.2.0307.1501")] diff --git a/Disco.Services/Properties/AssemblyInfo.cs b/Disco.Services/Properties/AssemblyInfo.cs index d839056b..fed0a480 100644 --- a/Disco.Services/Properties/AssemblyInfo.cs +++ b/Disco.Services/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0304.2046")] -[assembly: AssemblyFileVersion("1.2.0304.2046")] +[assembly: AssemblyVersion("1.2.0307.1501")] +[assembly: AssemblyFileVersion("1.2.0307.1501")] diff --git a/Disco.Web.Extensions/Properties/AssemblyInfo.cs b/Disco.Web.Extensions/Properties/AssemblyInfo.cs index ebaff29d..57f9d979 100644 --- a/Disco.Web.Extensions/Properties/AssemblyInfo.cs +++ b/Disco.Web.Extensions/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0304.2046")] -[assembly: AssemblyFileVersion("1.2.0304.2046")] +[assembly: AssemblyVersion("1.2.0307.1501")] +[assembly: AssemblyFileVersion("1.2.0307.1501")] diff --git a/Disco.Web/Areas/Config/Views/DocumentTemplate/UndetectedPages.cshtml b/Disco.Web/Areas/Config/Views/DocumentTemplate/UndetectedPages.cshtml index 5e1a5e45..12808a18 100644 --- a/Disco.Web/Areas/Config/Views/DocumentTemplate/UndetectedPages.cshtml +++ b/Disco.Web/Areas/Config/Views/DocumentTemplate/UndetectedPages.cshtml @@ -68,7 +68,7 @@ var urlImporterUndetectedDelete = '@(Url.Action(MVC.API.DocumentTemplate.ImporterUndetectedDelete()))/'; var $undetectedPageDialog = $('#undetectedPageDialog').dialog({ modal: true, - height: 820, + height: 850, width: 800, resizable: false, autoOpen: false @@ -92,7 +92,7 @@ var oldSelectedIndex = vm.undetectedPages.indexOf(oldSelected); if (vm.undetectedPages().length > 1) { - if (oldSelectedIndex > vm.undetectedPages().length - 1) + if (oldSelectedIndex + 1 <= vm.undetectedPages().length - 1) vm.selectedUndetectedPage(vm.undetectedPages()[oldSelectedIndex + 1]); else vm.selectedUndetectedPage(vm.undetectedPages()[oldSelectedIndex - 1]); diff --git a/Disco.Web/Areas/Config/Views/DocumentTemplate/UndetectedPages.generated.cs b/Disco.Web/Areas/Config/Views/DocumentTemplate/UndetectedPages.generated.cs index f8d93168..945ac76a 100644 --- a/Disco.Web/Areas/Config/Views/DocumentTemplate/UndetectedPages.generated.cs +++ b/Disco.Web/Areas/Config/Views/DocumentTemplate/UndetectedPages.generated.cs @@ -31,9 +31,9 @@ namespace Disco.Web.Areas.Config.Views.DocumentTemplate using Disco.Web; using Disco.Web.Extensions; - [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "1.5.0.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "1.5.4.0")] [System.Web.WebPages.PageVirtualPathAttribute("~/Areas/Config/Views/DocumentTemplate/UndetectedPages.cshtml")] - public class UndetectedPages : System.Web.Mvc.WebViewPage + public partial class UndetectedPages : System.Web.Mvc.WebViewPage { public UndetectedPages() { @@ -245,7 +245,7 @@ WriteLiteral("/\';\r\n var urlImporterUndetectedDelete = \'"); #line default #line hidden WriteLiteral("/\';\r\n var $undetectedPageDialog = $(\'#undetectedPageDialog\').dialog({\r\n " + -" modal: true,\r\n height: 820,\r\n width: 800,\r\n " + +" modal: true,\r\n height: 850,\r\n width: 800,\r\n " + " resizable: false,\r\n autoOpen: false\r\n });\r\n\r\n $di" + "alogRemove = $(\'#dialogRemove\').dialog({\r\n resizable: false,\r\n " + " height: 140,\r\n modal: true,\r\n autoOpen: false\r\n " + @@ -256,66 +256,66 @@ WriteLiteral("/\';\r\n var $undetectedPageDialog = $(\'#undetectedPageDia " self.selectNextPage = function () {\r\n var oldSelected = self.sel" + "ectedUndetectedPage();\r\n var oldSelectedIndex = vm.undetectedPage" + "s.indexOf(oldSelected);\r\n\r\n if (vm.undetectedPages().length > 1) " + -"{\r\n if (oldSelectedIndex > vm.undetectedPages().length - 1)\r\n" + -" vm.selectedUndetectedPage(vm.undetectedPages()[oldSelect" + -"edIndex + 1]);\r\n else\r\n vm.selectedUnd" + -"etectedPage(vm.undetectedPages()[oldSelectedIndex - 1]);\r\n } else" + -" {\r\n $undetectedPageDialog.dialog(\'close\');\r\n " + -" vm.selectedUndetectedPage(null);\r\n }\r\n }\r\n " + -"}\r\n\r\n function undetectedPageViewModel(id, timestamp, timestampFuzzy) {\r\n" + -" var self = this;\r\n\r\n self.id = id;\r\n self.time" + -"stamp = timestamp;\r\n self.timestampFuzzy = timestampFuzzy;\r\n " + -" self.thumbnailUrl = \"url(\" + urlUndetectedPageThumbnail + \"&id=\" + id + \")\";\r" + -"\n self.previewUrl = \"url(\" + urlUndetectedPagePreview + \"&id=\" + id +" + -" \")\";\r\n self.sourceUrl = urlUndetectedPageSource + \"&id=\" + id;\r\n " + -" self.select = function (e, d) {\r\n vm.selectedUndetectedPa" + -"ge(self);\r\n $undetectedPageDialog.dialog(\'open\');\r\n }\r" + -"\n\r\n // Dialog Properties\r\n self.dialogTemplateId = ko.obse" + -"rvable(null);\r\n self.dialogDataId = ko.observable(null);\r\n " + -" self.dialogDataIdService = ko.computed(function () {\r\n return ur" + -"lDataIdLookupService + self.dialogTemplateId();\r\n });\r\n se" + -"lf.deletePage = function () {\r\n $undetectedPageDialog.dialog(\'opt" + -"ion\', \'disabled\', true);\r\n\r\n $dialogRemove.dialog(\'option\', \'butt" + -"ons\', {\r\n \"Remove\": function () {\r\n $d" + -"ialogRemove.dialog(\"close\");\r\n var data = { id: self.id }" + -";\r\n $.ajax({\r\n url: urlImporte" + -"rUndetectedDelete,\r\n dataType: \'json\',\r\n " + -" data: data,\r\n type: \'POST\',\r\n " + -" success: function (d) {\r\n if (" + -"d == \'OK\') {\r\n vm.selectNextPage();\r\n " + -" vm.undetectedPages.remove(self);\r\n " + -" } else {\r\n alert(\'Unable to del" + -"ete page: \' + d);\r\n }\r\n " + -" $undetectedPageDialog.dialog(\'option\', \'disabled\', false);\r\n " + -" },\r\n error: function (jqXHR, textStatus" + -", errorThrown) {\r\n alert(\'Unable to delete page: " + -"\' + errorThrown);\r\n $undetectedPageDialog.dialog(" + -"\'option\', \'disabled\', false);\r\n }\r\n " + -" });\r\n },\r\n \"Cancel\": function () {\r\n" + -" $dialogRemove.dialog(\"close\");\r\n " + -"$undetectedPageDialog.dialog(\'option\', \'disabled\', false);\r\n " + -"}\r\n });\r\n\r\n $dialogRemove.dialog(\'open\');\r\n\r\n " + -" return false;\r\n }\r\n self.assignPage = function " + -"() {\r\n var dtId = self.dialogTemplateId();\r\n var d" + -"Id = self.dialogDataId();\r\n if (!dtId || !dId) {\r\n " + -" alert(\'Please specify a valid Document Type and Data Id\');\r\n " + -" } else {\r\n $undetectedPageDialog.dialog(\'option\', \'disabled\'" + -", true);\r\n\r\n var data = { id: self.id, DocumentTemplateId: dt" + -"Id, DataId: dId };\r\n\r\n $.ajax({\r\n url:" + -" urlImporterUndetectedAssign,\r\n dataType: \'json\',\r\n " + -" data: data,\r\n type: \'POST\',\r\n " + -" success: function (d) {\r\n if (d == \'OK\'" + -") {\r\n vm.selectNextPage();\r\n " + -" vm.undetectedPages.remove(self);\r\n } else " + -"{\r\n alert(\'Unable to assign page: \' + d);\r\n " + -" }\r\n $undetectedPageDialog.dialo" + -"g(\'option\', \'disabled\', false);\r\n },\r\n " + -" error: function (jqXHR, textStatus, errorThrown) {\r\n " + -" alert(\'Unable to assign page: \' + errorThrown);\r\n " + -" $undetectedPageDialog.dialog(\'option\', \'disabled\', false);\r\n " + -" }\r\n });\r\n\r\n }\r\n return fa" + -"lse;\r\n };\r\n }\r\n\r\n function init() {\r\n vm = n" + -"ew pageViewModel();\r\n\r\n $.ajax({\r\n url: \'"); +"{\r\n if (oldSelectedIndex + 1 <= vm.undetectedPages().length -" + +" 1)\r\n vm.selectedUndetectedPage(vm.undetectedPages()[oldS" + +"electedIndex + 1]);\r\n else\r\n vm.select" + +"edUndetectedPage(vm.undetectedPages()[oldSelectedIndex - 1]);\r\n }" + +" else {\r\n $undetectedPageDialog.dialog(\'close\');\r\n " + +" vm.selectedUndetectedPage(null);\r\n }\r\n }\r\n " + +" }\r\n\r\n function undetectedPageViewModel(id, timestamp, timestampFuzzy" + +") {\r\n var self = this;\r\n\r\n self.id = id;\r\n self" + +".timestamp = timestamp;\r\n self.timestampFuzzy = timestampFuzzy;\r\n " + +" self.thumbnailUrl = \"url(\" + urlUndetectedPageThumbnail + \"&id=\" + id + " + +"\")\";\r\n self.previewUrl = \"url(\" + urlUndetectedPagePreview + \"&id=\" +" + +" id + \")\";\r\n self.sourceUrl = urlUndetectedPageSource + \"&id=\" + id;\r" + +"\n self.select = function (e, d) {\r\n vm.selectedUndetec" + +"tedPage(self);\r\n $undetectedPageDialog.dialog(\'open\');\r\n " + +" }\r\n\r\n // Dialog Properties\r\n self.dialogTemplateId = ko" + +".observable(null);\r\n self.dialogDataId = ko.observable(null);\r\n " + +" self.dialogDataIdService = ko.computed(function () {\r\n retu" + +"rn urlDataIdLookupService + self.dialogTemplateId();\r\n });\r\n " + +" self.deletePage = function () {\r\n $undetectedPageDialog.dialog" + +"(\'option\', \'disabled\', true);\r\n\r\n $dialogRemove.dialog(\'option\', " + +"\'buttons\', {\r\n \"Remove\": function () {\r\n " + +" $dialogRemove.dialog(\"close\");\r\n var data = { id: self" + +".id };\r\n $.ajax({\r\n url: urlIm" + +"porterUndetectedDelete,\r\n dataType: \'json\',\r\n " + +" data: data,\r\n type: \'POST\',\r\n " + +" success: function (d) {\r\n " + +" if (d == \'OK\') {\r\n vm.selectNextPage();\r\n " + +" vm.undetectedPages.remove(self);\r\n " + +" } else {\r\n alert(\'Unable t" + +"o delete page: \' + d);\r\n }\r\n " + +" $undetectedPageDialog.dialog(\'option\', \'disabled\', false);\r\n " + +" },\r\n error: function (jqXHR, textS" + +"tatus, errorThrown) {\r\n alert(\'Unable to delete p" + +"age: \' + errorThrown);\r\n $undetectedPageDialog.di" + +"alog(\'option\', \'disabled\', false);\r\n }\r\n " + +" });\r\n },\r\n \"Cancel\": function (" + +") {\r\n $dialogRemove.dialog(\"close\");\r\n " + +" $undetectedPageDialog.dialog(\'option\', \'disabled\', false);\r\n " + +" }\r\n });\r\n\r\n $dialogRemove.dialog(\'open\');\r\n\r\n" + +" return false;\r\n }\r\n self.assignPage = func" + +"tion () {\r\n var dtId = self.dialogTemplateId();\r\n " + +"var dId = self.dialogDataId();\r\n if (!dtId || !dId) {\r\n " + +" alert(\'Please specify a valid Document Type and Data Id\');\r\n " + +" } else {\r\n $undetectedPageDialog.dialog(\'option\', \'disa" + +"bled\', true);\r\n\r\n var data = { id: self.id, DocumentTemplateI" + +"d: dtId, DataId: dId };\r\n\r\n $.ajax({\r\n " + +" url: urlImporterUndetectedAssign,\r\n dataType: \'json\',\r\n " + +" data: data,\r\n type: \'POST\',\r\n " + +" success: function (d) {\r\n if (d ==" + +" \'OK\') {\r\n vm.selectNextPage();\r\n " + +" vm.undetectedPages.remove(self);\r\n } " + +"else {\r\n alert(\'Unable to assign page: \' + d);\r\n " + +" }\r\n $undetectedPageDialog." + +"dialog(\'option\', \'disabled\', false);\r\n },\r\n " + +" error: function (jqXHR, textStatus, errorThrown) {\r\n " + +" alert(\'Unable to assign page: \' + errorThrown);\r\n " + +" $undetectedPageDialog.dialog(\'option\', \'disabled\', false);\r\n " + +" }\r\n });\r\n\r\n }\r\n retu" + +"rn false;\r\n };\r\n }\r\n\r\n function init() {\r\n v" + +"m = new pageViewModel();\r\n\r\n $.ajax({\r\n url: \'"); #line 202 "..\..\Areas\Config\Views\DocumentTemplate\UndetectedPages.cshtml" diff --git a/Disco.Web/Properties/AssemblyInfo.cs b/Disco.Web/Properties/AssemblyInfo.cs index f863e67d..98bea942 100644 --- a/Disco.Web/Properties/AssemblyInfo.cs +++ b/Disco.Web/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.2.0304.2046")] -[assembly: AssemblyFileVersion("1.2.0304.2046")] +[assembly: AssemblyVersion("1.2.0307.1501")] +[assembly: AssemblyFileVersion("1.2.0307.1501")]