Beispiel #1
0
        private static void ResizeImage(int width, int height, int quality, string options, IMagickImage magickImage)
        {
            magickImage.Quality = quality;
            magickImage.Strip();

            if (options.Contains("g")) //grayscale
            {
                magickImage.Grayscale(PixelIntensityMethod.Average);
            }
            if (width == magickImage.BaseWidth && height == magickImage.BaseHeight)
            {
                return;
            }
            else if (options.Contains("f") || options.Contains("t"))
            {
                magickImage.Resize(width, height);
            }
            else
            {
                var magickGeometry = new MagickGeometry(width, height)
                {
                    IgnoreAspectRatio = false, //保持长宽比
                    FillArea          = true
                };
                magickImage.Resize(magickGeometry);
                magickImage.Crop(magickGeometry, Gravity.Center);
            }
        }
Beispiel #2
0
        /// <summary>
        /// Generate compress result from base64 image.
        /// </summary>
        /// <param name="base64Image"></param>
        /// <returns></returns>
        public CompressedImageModel CompressFromBase64(string base64Image)
        {
            MagickFormat format = MagickFormat.Jpeg;
            IMagickImage image  = MagickImage.FromBase64(base64Image);

            MemoryStream compressedBuffer = new MemoryStream();
            MemoryStream thumbnailBuffer  = new MemoryStream();

            image.Write(compressedBuffer, format);

            image.Resize(ImageService.ThumbnailAspectSize, ImageService.ThumbnailAspectSize);
            image.Write(thumbnailBuffer, format);

            compressedBuffer.Seek(0, SeekOrigin.Begin);
            thumbnailBuffer.Seek(0, SeekOrigin.Begin);

            this.ImageOptimizer.LosslessCompress(compressedBuffer);
            this.ImageOptimizer.LosslessCompress(thumbnailBuffer);

            return(new CompressedImageModel(
                       compressedBuffer.GetBuffer(),
                       thumbnailBuffer.GetBuffer(),
                       ImageService.ImageFormat
                       ));
        }
Beispiel #3
0
        private static void ResizeImage(int requestWidth, int requestHeight, int quality, string options, IMagickImage image)
        {
            image.Quality = quality;

            image.Strip();

            if (options.Contains("g")) //grayscale
            {
                image.Grayscale(PixelIntensityMethod.Average);
            }

            if (image.BaseWidth == requestWidth && image.BaseHeight == requestHeight) //requested image is same size
            {
                return;
            }
            if (requestWidth == 0 && requestHeight == 0) //requested image is same size
            {
                return;
            }

            if (options.Contains("f") || options.Contains("t")) //scale with aspect of image
            {
                var size = new MagickGeometry(requestWidth, requestHeight);
                image.Resize(size);
            }
            else if (requestWidth == 0 || requestHeight == 0) //scale with aspect of image
            {
                var size = new MagickGeometry(requestWidth, requestHeight);
                image.Resize(size);
            }
            else // This will resize the image to a fixed size without maintaining the aspect ratio.
            {
                var size = new MagickGeometry(requestWidth, requestHeight)
                {
                    IgnoreAspectRatio = false, //keep aspect ratio!
                    FillArea          = true
                };
                image.Resize(size);
                image.Crop(size, Gravity.Center);
            }
        }
Beispiel #4
0
        public override void ShowPreview(Renderer renderer)
        {
            if (NeedleImage != null)
            {
                var pixelMagnification = 8.0;
                var magnification      = Math.Min(1 / (double)pixelMagnification, 1.0);
                var resizeAmount       = new Percentage(magnification * 100);

                IMagickImage newHaystack = null;
                var          haystack    = this.state.Image(this.haystack);
                lock (haystack) {
                    newHaystack = haystack.Clone();
                    newHaystack.Resize(resizeAmount);
                };
                var scaleFactor = newHaystack.Width / haystack.Width;
                haystack = newHaystack;

                if (this.initialCandidates != null)
                {
                    foreach (var candidate in this.initialCandidates)
                    {
                        var rect = new Drawables()
                                   .StrokeWidth(1)
                                   .StrokeColor(new MagickColor("blue"))
                                   .FillOpacity(new Percentage(0))
                                   .Rectangle(candidate.X * scaleFactor, candidate.Y * scaleFactor, (candidate.X + NeedleSize) * scaleFactor, (candidate.Y + NeedleSize) * scaleFactor);
                        haystack.Draw(rect);
                    }
                }

                if (this.searchResult != null && this.searchResult.Distance < SearchResult.MAX_DISTANCE)
                {
                    var joinPoint = this.searchResult.HaystackPoint;
                    var x         = joinPoint.X;
                    var y         = joinPoint.Y;

                    var rect = new Drawables()
                               .StrokeWidth(1)
                               .StrokeColor(searchResult.MeetsThreshold() ? new MagickColor("green") : new MagickColor("yellow"))
                               .FillOpacity(new Percentage(0))
                               .Rectangle(x * scaleFactor, y * scaleFactor, (x + NeedleSize) * scaleFactor, (y + NeedleSize) * scaleFactor);
                    haystack.Draw(rect);
                }
                var needle = NeedleImage.Clone();
                needle.Resize(resizeAmount);
                renderer.DisplayImages(haystack, needle);
            }
        }
Beispiel #5
0
        public Stream Compare()
        {
            IMagickImage modifiedHigh = ReadPDF(_modifiedImagePath, _magickSettingsHigh);
            IMagickImage original     = ReadPDF(_originalImagePath, _magickSettings);
            IMagickImage modified     = ReadPDF(_modifiedImagePath, _magickSettings);

            IMagickImage mask = GenerateMask(original, modified);

            mask.Resize(new Percentage((double)Density / (double)ReducedDensity * 100d));
            modifiedHigh.Composite(mask, Gravity.Center, CompositeOperator.Over);

            MemoryStream ms = new MemoryStream();

            modifiedHigh.Write(ms);
            ms.Position = 0;

            return(ms);
        }
Beispiel #6
0
        void DoMagic(ImageEditMode mode, IMagickImage image, int originalWidth, int originalHeight)
        {
            switch (mode)
            {
            case ImageEditMode.Swirl:
                image.Swirl(360);
                break;

            case ImageEditMode.Rescale:
                image.LiquidRescale(image.Width / 2, image.Height / 2);
                image.LiquidRescale((image.Width * 3) / 2, (image.Height * 3) / 2);
                image.Resize(originalWidth, originalHeight);
                break;

            case ImageEditMode.Wave:
                image.BackgroundColor = MagickColor.FromRgb(0, 0, 0);
                image.Wave(image.Interpolate, 10.0, 150.0);
                break;

            case ImageEditMode.Implode:
                image.Implode(0.5d, PixelInterpolateMethod.Average);
                break;

            case ImageEditMode.JPEG:
                image.Quality = 10;
                break;

            case ImageEditMode.MoreJPEG:
                image.Quality = 5;
                break;

            case ImageEditMode.MostJPEG:
                image.Quality = 1;
                break;

            default:
                break;
            }
        }
        private void Save(IMagickImage image, string device, string uploadPath, string identifier, MagickFormat format, int?size = null)
        {
            image.Format = format;

            if (size != null)
            {
                image.Resize(new MagickGeometry()
                {
                    Width             = size.Value,
                    IgnoreAspectRatio = false
                });
            }

            string path = Path.Combine(uploadPath, String.Format("{0}-{1}.{2}", identifier, device, format.ToString()));

            image.Write(path);

            if (format == MagickFormat.Jpg)
            {
                Compress(path);
            }
        }
Beispiel #8
0
        private void DrawTreeCluster(MagickImage overlay, DrawableFixture fixture)
        {
            //MainForm.Log(string.Format("Image: {0} ({1}) ...", fixture.Name, fixture.TreeCluster.Tree), MainForm.LogLevel.notice);
            string fileName    = System.IO.Path.GetFileNameWithoutExtension(fixture.TreeCluster.Tree);
            string defaultTree = "elm1";

            // Load model image
            if (!m_modelImages.ContainsKey(fileName))
            {
                string treeImageFile = string.Format("{0}\\data\\prerendered\\trees\\{1}.png", System.Windows.Forms.Application.StartupPath, fileName);
                if (System.IO.File.Exists(treeImageFile))
                {
                    MagickImage modelImage = new MagickImage(treeImageFile);
                    modelImage.Blur();
                    m_modelImages.Add(fileName, modelImage);
                }
                else
                {
                    MainForm.Log(string.Format("Can not find image for tree {0} ({1}), using default tree", fixture.TreeCluster.Tree, fixture.NifName), MainForm.LogLevel.warning);
                    m_modelImages.Add(fileName, m_modelImages[defaultTree]);
                }
            }

            if (m_modelImages.ContainsKey(fileName) && m_modelImages[fileName] != null)
            {
                // Get the width of the orginal tree shape
                NifRow tree = FixturesLoader.NifRows.Where(n => n.Filename.ToLower() == fixture.TreeCluster.Tree.ToLower()).FirstOrDefault();
                if (tree == null)
                {
                    return;
                }

                System.Drawing.SizeF treeSize = tree.GetSize(0, 0);

                int dimensions     = ((fixture.CanvasWidth > fixture.CanvasHeight) ? fixture.CanvasWidth : fixture.CanvasHeight) + 10;
                int extendedWidth  = dimensions - fixture.CanvasWidth;
                int extendedHeight = dimensions - fixture.CanvasHeight;

                using (MagickImage treeCluster = new MagickImage(MagickColors.Transparent, dimensions, dimensions))
                {
                    double centerX = treeCluster.Width / 2d;
                    double centerY = treeCluster.Height / 2d;

                    foreach (SharpDX.Vector3 treeInstance in fixture.TreeCluster.TreeInstances)
                    {
                        using (IMagickImage treeImage = m_modelImages[fileName].Clone())
                        {
                            double scaleWidthToTreeImage  = treeSize.Width / treeImage.Width;
                            double scaleHeightToTreeImage = treeSize.Height / treeImage.Height;
                            int    width  = Convert.ToInt32(treeImage.Width * scaleWidthToTreeImage * fixture.Scale);
                            int    height = Convert.ToInt32(treeImage.Height * scaleHeightToTreeImage * fixture.Scale);
                            treeImage.Resize(width, height);

                            int x = Convert.ToInt32(centerX - width / 2d - zoneConfiguration.ZoneCoordinateToMapCoordinate(treeInstance.X) * (fixture.FixtureRow.Scale / 100));
                            int y = Convert.ToInt32(centerY - height / 2d - zoneConfiguration.ZoneCoordinateToMapCoordinate(treeInstance.Y) * (fixture.FixtureRow.Scale / 100));
                            treeCluster.Composite(treeImage, x, y, CompositeOperator.SrcOver);
                        }
                    }

                    treeCluster.Rotate((360d * fixture.FixtureRow.AxisZ3D - fixture.FixtureRow.A) * -1);

                    using (MagickImage modelCanvas = new MagickImage(MagickColors.Transparent, fixture.CanvasWidth, fixture.CanvasHeight))
                    {
                        foreach (DrawableElement drawableElement in fixture.DrawableElements)
                        {
                            modelCanvas.Settings.FillColor = new MagickColor(
                                Convert.ToUInt16(128 * 256 * drawableElement.lightning),
                                Convert.ToUInt16(128 * 256 * drawableElement.lightning),
                                Convert.ToUInt16(128 * 256 * drawableElement.lightning)
                                );

                            DrawablePolygon polyDraw = new DrawablePolygon(drawableElement.coordinates);
                            modelCanvas.Draw(polyDraw);
                        }

                        modelCanvas.Composite(treeCluster, Gravity.Center, CompositeOperator.DstIn);
                        treeCluster.Composite(modelCanvas, Gravity.Center, CompositeOperator.Overlay);
                        //treeCluster.Composite(modelCanvas, Gravity.Center, CompositeOperator.SrcOver);
                    }

                    if (fixture.RendererConf.HasShadow)
                    {
                        CastShadow(
                            treeCluster,
                            fixture.RendererConf.ShadowOffsetX,
                            fixture.RendererConf.ShadowOffsetY,
                            fixture.RendererConf.ShadowSize,
                            new Percentage(100 - fixture.RendererConf.ShadowTransparency),
                            fixture.RendererConf.ShadowColor,
                            false
                            );
                    }

                    if (fixture.RendererConf.Transparency != 0)
                    {
                        treeCluster.Alpha(AlphaOption.Set);

                        double divideValue = 100.0 / (100.0 - fixture.RendererConf.Transparency);
                        treeCluster.Evaluate(Channels.Alpha, EvaluateOperator.Divide, divideValue);
                    }

                    overlay.Composite(treeCluster, Convert.ToInt32(fixture.CanvasX - extendedWidth / 2), Convert.ToInt32(fixture.CanvasY - extendedHeight / 2), CompositeOperator.SrcOver);
                }
            }
        }
Beispiel #9
0
        private void DrawImage(MagickImage overlay, DrawableFixture fixture)
        {
            //MainForm.Log(string.Format("Image: {0} ({1}) ...", fixture.Name, fixture.NifName), MainForm.LogLevel.notice);
            string fileName    = System.IO.Path.GetFileNameWithoutExtension(fixture.NifName);
            string defaultTree = "elm1";

            // Load default tree
            if (!m_modelImages.ContainsKey(defaultTree))
            {
                string defaultTreeImage = string.Format("{0}\\data\\prerendered\\trees\\{1}.png", System.Windows.Forms.Application.StartupPath, defaultTree);
                if (System.IO.File.Exists(defaultTreeImage))
                {
                    MagickImage treeImage = new MagickImage(defaultTreeImage);
                    treeImage.Blur();
                    m_modelImages.Add(defaultTree, treeImage);
                }
                else
                {
                    m_modelImages.Add(fileName, null);
                }
            }

            // TreeClusters are sets of trees in a specified arrangement
            // They need to be drawe separately
            if (fixture.IsTreeCluster)
            {
                DrawTreeCluster(overlay, fixture);
                return;
            }

            // Load model image
            if (!m_modelImages.ContainsKey(fileName))
            {
                string objectImageFile = string.Format("{0}\\data\\prerendered\\objects\\{1}.png", System.Windows.Forms.Application.StartupPath, fileName);
                if (fixture.IsTree)
                {
                    objectImageFile = string.Format("{0}\\data\\prerendered\\trees\\{1}.png", System.Windows.Forms.Application.StartupPath, fileName);
                }

                if (System.IO.File.Exists(objectImageFile))
                {
                    MagickImage objectImage = new MagickImage(objectImageFile);
                    if (fixture.IsTree)
                    {
                        objectImage.Blur();
                    }
                    m_modelImages.Add(fileName, objectImage);
                }
                else
                {
                    if (fixture.IsTree)
                    {
                        MainForm.Log(string.Format("Can not find image for tree {0} ({1}), using default tree", fixture.Name, fixture.NifName), MainForm.LogLevel.warning);
                        m_modelImages.Add(fileName, m_modelImages[defaultTree]);
                    }
                    else
                    {
                        m_modelImages.Add(fileName, null);
                    }
                }
            }

            // Draw the image
            if (m_modelImages.ContainsKey(fileName) && m_modelImages[fileName] != null)
            {
                NifRow orginalNif = FixturesLoader.NifRows.Where(n => n.NifId == fixture.FixtureRow.NifId).FirstOrDefault();
                if (orginalNif == null)
                {
                    MainForm.Log(string.Format("Error with imaged nif ({0})!", fixture.FixtureRow.TextualName), MainForm.LogLevel.warning);
                }

                System.Drawing.SizeF objectSize = orginalNif.GetSize(0, 0);

                // The final image
                using (MagickImage modelImage = new MagickImage(MagickColors.Transparent, fixture.CanvasWidth, fixture.CanvasHeight))
                {
                    // Place the replacing image
                    using (IMagickImage newModelImage = m_modelImages[fileName].Clone())
                    {
                        newModelImage.BackgroundColor = MagickColors.Transparent;

                        double scaleWidthToTreeImage  = objectSize.Width / newModelImage.Width;
                        double scaleHeightToTreeImage = objectSize.Height / newModelImage.Height;
                        int    width  = Convert.ToInt32(newModelImage.Width * scaleWidthToTreeImage * fixture.Scale);
                        int    height = Convert.ToInt32(newModelImage.Height * scaleHeightToTreeImage * fixture.Scale);

                        // Resize to new size
                        newModelImage.FilterType         = FilterType.Gaussian;
                        newModelImage.VirtualPixelMethod = VirtualPixelMethod.Transparent;
                        newModelImage.Resize(width, height);

                        // Rotate the image
                        //newModelImage.Rotate(fixture.FixtureRow.A * -1 * fixture.FixtureRow.AxisZ3D);
                        newModelImage.Rotate((360d * fixture.FixtureRow.AxisZ3D - fixture.FixtureRow.A) * -1);

                        // Place in center of modelImage
                        modelImage.Composite(newModelImage, Gravity.Center, CompositeOperator.SrcOver);
                    }

                    // Draw the shaped model if wanted
                    if (fixture.RendererConf.HasLight)
                    {
                        using (MagickImage modelShaped = new MagickImage(MagickColors.Transparent, fixture.CanvasWidth, fixture.CanvasHeight))
                        {
                            foreach (DrawableElement drawableElement in fixture.DrawableElements)
                            {
                                var light = 1 - drawableElement.lightning;
                                modelShaped.Settings.FillColor = new MagickColor(
                                    Convert.ToUInt16(ushort.MaxValue * light),
                                    Convert.ToUInt16(ushort.MaxValue * light),
                                    Convert.ToUInt16(ushort.MaxValue * light)
                                    );

                                DrawablePolygon polyDraw = new DrawablePolygon(drawableElement.coordinates);
                                modelShaped.Draw(polyDraw);
                            }

                            using (MagickImage modelMask = new MagickImage(MagickColors.Transparent, fixture.CanvasWidth, fixture.CanvasHeight))
                            {
                                modelShaped.Blur();
                                modelMask.Composite(modelShaped, 0, 0, CompositeOperator.DstAtop);
                                modelMask.Composite(modelImage, 0, 0, CompositeOperator.DstIn);
                                modelMask.Level(new Percentage(20), new Percentage(100), Channels.All);
                                modelImage.Composite(modelMask, 0, 0, CompositeOperator.ColorDodge);
                            }
                        }
                    }

                    // Add the shadow if not a tree (tree shadow are substituted by a treeoverlay)
                    if (fixture.RendererConf.HasShadow && !fixture.IsTree)
                    {
                        CastShadow(
                            modelImage,
                            fixture.RendererConf.ShadowOffsetX,
                            fixture.RendererConf.ShadowOffsetY,
                            fixture.RendererConf.ShadowSize,
                            new Percentage(100 - fixture.RendererConf.ShadowTransparency),
                            fixture.RendererConf.ShadowColor
                            );

                        // Update the canvas position to match the new border
                        fixture.CanvasX -= fixture.RendererConf.ShadowSize;
                        fixture.CanvasY -= fixture.RendererConf.ShadowSize;
                    }

                    // Set transprency if not a tree (see shadow)
                    if (fixture.RendererConf.Transparency != 0 && !fixture.IsTree)
                    {
                        double divideValue = 100.0 / (100.0 - fixture.RendererConf.Transparency);
                        modelImage.Evaluate(Channels.Alpha, EvaluateOperator.Divide, divideValue);
                    }

                    // Place the image on the right position
                    overlay.Composite(modelImage, Convert.ToInt32(fixture.CanvasX), Convert.ToInt32(fixture.CanvasY), CompositeOperator.SrcOver);
                }
            }
        }
Beispiel #10
0
        async Task DoImageMagickCommandForGif(CommandContext ctx, byte[] buffer, ImageEditMode mode)
        {
            if (mode == ImageEditMode.Rescale)
            {
                await ctx.RespondAsync("이 모드는 속도가 느리고 이미지사이즈에 기반해서 느려지기 때문에 gif는 지원하지 않습니다.");

                return;
            }
            MagickImageCollection image;

            try
            {
                image = new MagickImageCollection(buffer);
            }
            catch (MagickMissingDelegateErrorException)
            {
                await ctx.RespondAsync("이미지 파일확장자를 알아볼 수 없다.");

                return;
            }
            int originalWidth = image[0].Width, originalHeight = image[0].Height;

            if (originalHeight * originalWidth > 1000000)
            {
                await ctx.RespondAsync($"Gif exceeds maximum size of 1000000 pixels (Actual size: {originalHeight * originalWidth})");

                return;
            }
            if (image.Count > 100)
            {
                await ctx.RespondAsync($"Gif exceeds maximum frame count of 100 pixels (Actual count: {image.Count})");

                return;
            }
            image.Coalesce();
            long rawLength;

            using (MemoryStream stream = new MemoryStream())
            {
                image.Write(stream);
                rawLength = stream.Length;
            }
            double exceed  = rawLength / 4194304d;
            double rescale = 1f;

            if (exceed > 1.0)
            {
                rescale = Math.Sqrt(exceed);
            }
            await ctx.TriggerTypingAsync();

            for (int i = 0; i < image.Count; i++)
            {
                IMagickImage frame = image[i];
                if (rescale > 1f)
                {
                    if (rescale > 2f)
                    {
                        frame.AdaptiveResize((int)(frame.Width / rescale), (int)(frame.Height / rescale));
                    }
                    else
                    {
                        frame.Resize((int)(frame.Width / rescale), (int)(frame.Height / rescale));
                    }
                }
                DoMagic(mode, frame, originalWidth, originalHeight);
            }
            await ctx.TriggerTypingAsync();

            image.OptimizeTransparency();
            using (Stream stream = new MemoryStream())
            {
                image.Write(stream);
                stream.Seek(0, SeekOrigin.Begin);
                await ctx.RespondWithFileAsync(stream, "magic.gif");
            }
        }
Beispiel #11
0
        // TODO: Name of this method + signature is a mess
        private SearchResult FindTemplateInImage(IMagickImage needleImage, Gravity needleGravity, IMagickImage haystack, StitchTask task, Point anchor)
        {
            // Resize needle
            var pixelMagnification = 8.0;
            var magnification      = Math.Min(1 / (double)pixelMagnification, 1.0);
            var progress           = (IProgress <double>)task;

            var resizeAmount = new Percentage(magnification * 100);
            var template     = needleImage.Clone();

            template.Resize(resizeAmount);
            template.RePage();

            IMagickImage searchArea = null;
            var          resized    = false;

            while (!resized)
            {
                try
                {
                    lock (haystack)
                    {
                        searchArea = haystack.Clone();
                        searchArea.Resize(resizeAmount);
                        searchArea.RePage();
                    }
                    resized = true;
                } catch (AccessViolationException)
                {
                    Console.WriteLine("Corrupt Memory, trying again");
                    Thread.Sleep(500);
                }
            }

            // We need to get the actual values here, since Resize() has unexpected rounding logic
            // (only supports whole digit percentages) and if we don't use the actual values then
            // scaling math won't work properly.
            var oldWidth  = searchArea.Width;
            var oldHeight = searchArea.Height;

            var bounds     = new MagickGeometry(0, 0, searchArea.Width, searchArea.Height);
            var candidates = FindTemplateCandidates(searchArea, template, bounds, progress, 2000, new HashSet <Point>());

            if (candidates.Any())
            {
                var bestScore = candidates.First().Key;
                this.initialCandidates = candidates.Where(x => x.Key < bestScore * 1.05).Select(x => {
                    return(new Point(x.Value.X / magnification, x.Value.Y / magnification));
                }).ToList();
            }


            while (pixelMagnification > 1 && candidates.Any())
            {
                var newCandidates         = new SortedList <double, Point>(new DuplicateKeyComparer <double>());
                var newPixelMagnification = pixelMagnification / 2;
                var newMagnification      = Math.Min(1 / (double)newPixelMagnification, 1.0);
                var newResizeAmount       = new Percentage(newMagnification * 100);
                var threshold             = 2000.0;
                var bestSeen  = threshold;
                var bestScore = candidates.First().Key;
                var toLoop    = candidates.Where(x => x.Key < bestScore * 1.05);
                Console.WriteLine("Considering {0} candidates at {1}", toLoop.Count(), newMagnification);

                IMagickImage newHaystack = null;
                lock (haystack)
                {
                    newHaystack = haystack.Clone();
                    newHaystack.Resize(newResizeAmount);
                    newHaystack.RePage();
                }

                var t2 = needleImage.Clone();
                t2.Resize(newResizeAmount);
                t2.RePage();

                var cache = new HashSet <Point>();

                foreach (var candidate in toLoop)
                {
                    var point  = new Point(candidate.Value.X / oldWidth * newHaystack.Width, candidate.Value.Y / oldHeight * newHaystack.Height);
                    var margin = newPixelMagnification;

                    var clampedBounds = new MagickGeometry(
                        (int)(point.X - margin),
                        (int)(point.Y - margin),
                        (int)(NeedleSize * newMagnification + margin * 2),
                        (int)(NeedleSize * newMagnification + margin * 2)
                        );
                    clampedBounds.X      = Math.Max(0, clampedBounds.X);
                    clampedBounds.Y      = Math.Max(0, clampedBounds.Y);
                    clampedBounds.Width  = Math.Min(newHaystack.Width - clampedBounds.X, clampedBounds.Width);
                    clampedBounds.Height = Math.Min(newHaystack.Height - clampedBounds.Y, clampedBounds.Height);

                    var toAdd = FindTemplateCandidates(newHaystack, t2, clampedBounds, this, threshold, cache);
                    foreach (var add in toAdd)
                    {
                        newCandidates.Add(add.Key, add.Value);
                        if (add.Key < bestSeen)
                        {
                            bestSeen = add.Key;
                            Console.WriteLine("Updating best score: {0}", bestSeen);
                        }
                    }
                }
                candidates         = newCandidates;
                magnification      = newMagnification;
                pixelMagnification = newPixelMagnification;
                oldWidth           = newHaystack.Width;
                oldHeight          = newHaystack.Height;
            }

            Console.WriteLine("============ Final: {0}", candidates.Count);

            if (candidates.Any())
            {
                var bestCandidate = candidates.First();

                return(new SearchResult()
                {
                    Distance = bestCandidate.Key,
                    HaystackPoint = bestCandidate.Value,
                    NeedlePoint = anchor
                });
            }
            return(SearchResult.Null);
        }
        public static void Run([BlobTrigger("inbox/{inputFilename}", Connection = "StorageAccountConnString")] Stream inputStream, string inputFilename,
                               [Blob("outbox/out-{inputFilename}", FileAccess.Write, Connection = "StorageAccountConnString")] Stream outputStream, ILogger log)
        {
            log.LogInformation($"STEP: trigger fired for file:{inputFilename}, Size: {inputStream.Length} Bytes");

            using (MagickImage image = new MagickImage(inputStream))
            {
                log.LogInformation($"STEP: Read image {inputFilename} with {image.BaseWidth} x {image.BaseHeight} in {image.Format.ToString()}");

                // Step 0 - does nothing in the sample images I used
                image.AutoOrient();

                // Step 1 - Deskew
                image.BackgroundColor = MagickColor.FromRgb(0, 0, 0);
                image.Deskew(new Percentage(1)); // documentation suggests "A threshold of 40% works for most images" but 1% seems to work well
                IMagickImage rotatedImage = image.Clone();
                log.LogInformation($"STEP: Deskewed {inputFilename}");

                // Step 2 - Apply threshold to transform in black and white image
                image.AutoThreshold(AutoThresholdMethod.OTSU);
                log.LogInformation($"STEP: Applied OTSU to {inputFilename}");

                // Step 3 - find the regions (blocs) in the image and group them if large enough
                IEnumerable <ConnectedComponent> components = null;
                ConnectedComponentsSettings      ccs        = new ConnectedComponentsSettings();
                ccs.AreaThreshold = 500 * 500.0; // 500x500 -- seems to be pointless, many more regions are returned
                ccs.Connectivity  = 8;
                components        = image.ConnectedComponents(ccs);

                // if there are multiple blocs, consolidate them in a larger block
                if (components != null && components.Count() > 0)
                {
                    log.LogInformation($"STEP: Looked for regions in {inputFilename}, there are {components.Count()}");

                    // filter out the smaller rectangles, as the AreaThreshold parameter seems not to be working
                    List <ConnectedComponent> biggerComponents = components.Where(cc => cc.Height * cc.Width >= 250000 && cc.Height * cc.Width != image.Width * image.Height) /*.OrderByDescending(i => i.Height * i.Width)*/.ToList();
                    int topLeftX = biggerComponents[0].X, topLeftY = biggerComponents[0].Y, bottomRightX = biggerComponents[0].Width + topLeftX, bottomRightY = biggerComponents[0].Height + topLeftY;

                    foreach (ConnectedComponent cc in biggerComponents)
                    {
                        #region Debug -- draw the regions on the image
                        //DrawableStrokeColor strokeColor = new DrawableStrokeColor(new MagickColor("yellow"));
                        //DrawableStrokeWidth stokeWidth = new DrawableStrokeWidth(3);
                        //DrawableFillColor fillColor = new DrawableFillColor(new MagickColor(50, 50, 50, 128));
                        //DrawableRectangle dr = new DrawableRectangle(cc.X, cc.Y, cc.X + cc.Width, cc.Y + cc.Height);
                        //rotatedImage.Draw(dr, strokeColor, stokeWidth, fillColor);
                        #endregion

                        if (cc.X < topLeftX)
                        {
                            topLeftX = cc.X;
                        }

                        if (cc.Y < topLeftY)
                        {
                            topLeftY = cc.Y;
                        }

                        if (cc.X + cc.Width > bottomRightX)
                        {
                            bottomRightX = cc.X + cc.Width;
                        }

                        if (cc.Y + cc.Height > bottomRightY)
                        {
                            bottomRightY = cc.Y + cc.Height;
                        }
                    }

                    #region Debug -- draw the bounding box on the image
                    //DrawableStrokeColor strokeColor2 = new DrawableStrokeColor(new MagickColor("purple"));
                    //DrawableStrokeWidth stokeWidth2 = new DrawableStrokeWidth(3);
                    //DrawableFillColor fillColor2 = new DrawableFillColor(new MagickColor(50, 50, 50, 128));
                    //DrawableRectangle dr2 = new DrawableRectangle(topLeftX, topLeftY, bottomRightX, bottomRightY);
                    //rotatedImage.Draw(dr2, strokeColor2, stokeWidth2, fillColor2);
                    #endregion

                    // Step 4 - Crop the image
                    MagickGeometry mg = new MagickGeometry(topLeftX, topLeftY, bottomRightX - topLeftX, bottomRightY - topLeftY);
                    rotatedImage.RePage();  // this is needed because otherwise the crop is relative to the page information and sometimes this leads to an incorrect crop
                    rotatedImage.Crop(mg);

                    log.LogInformation($"STEP: Cropped {inputFilename} to fit existing large regions");
                }
                else
                {
                    log.LogInformation($"STEP: Looked for large regions in {inputFilename}, none were found, skipping crop");
                }

                // Step 5 - Resize the image to 1200px width (todo: move to configuration)
                int originalWidth  = rotatedImage.BaseWidth;
                int originalHeight = rotatedImage.BaseHeight;
                rotatedImage.Resize(1200, 0); // make width 1200, height proportional
                log.LogInformation($"STEP: Resized {inputFilename} from {originalWidth}x{originalHeight} to {image.BaseWidth}x{image.BaseHeight}");

                // Step 6 - write out as Jpeg with 70% quality
                rotatedImage.Format  = MagickFormat.Jpeg;
                rotatedImage.Quality = 70;
                rotatedImage.Write(outputStream);
                log.LogInformation($"STEP: Wrote out {inputFilename} as JPEG");
            }

            log.LogInformation($"STEP: Processing of {inputFilename} done");
        }