Example #1
0
        // Standard
        public CWatchImage(string name, int index, IMagickImage magickImage, double metricUpperBound)
        {
            Name             = name;
            Index            = index;
            MagickImage      = magickImage;
            HasAlpha         = MagickImage != null && MagickImage.HasAlpha;
            TransparencyRate = MagickImage != null?MagickImage.GetTransparencyRate() : 0;

            AlphaChannel     = null;
            MetricUpperBound = metricUpperBound;

            _PauseTicks = DateTime.MinValue.Ticks;

            if (HasAlpha)
            {
                // Does Separate() clone the channels? If so, does it dispose of them during this?
                var tmpMi = MagickImage.Separate().Last().Clone();
                tmpMi.Negate();
                AlphaChannel = new MagickImage(_AlphaReplacement, MagickImage.Width, MagickImage.Height);
                AlphaChannel.Composite(tmpMi, CompositeOperator.CopyAlpha);
                AlphaChannel.RePage();
                tmpMi.Dispose();

                MagickImage.ColorAlpha(_AlphaReplacement);
                MagickImage.RePage();
            }
        }
Example #2
0
        private void Distort(IMagickImage output, PointD[] corners, MagickGeometry inputDimensions, MagickGeometry trimmedDimensions, MagickColor backgroundColor)
        {
            MagickGeometry outputDimensions = GetDimensions(output, corners, inputDimensions, trimmedDimensions);

            double[] arguments = new double[16]
            {
                corners[0].X, corners[0].Y, 0, 0,
                corners[1].X, corners[1].Y, 0, outputDimensions.Height,
                corners[2].X, corners[2].Y, outputDimensions.Width, outputDimensions.Height,
                corners[3].X, corners[3].Y, outputDimensions.Width, 0
            };

            output.VirtualPixelMethod = VirtualPixelMethod.Background;
            output.BackgroundColor    = backgroundColor;
            if (!DisableViewportCrop)
            {
                output.SetArtifact("distort:viewport", GetViewport(arguments, corners).ToString());
            }
            var distortSettings = new DistortSettings()
            {
                Bestfit = true
            };

            output.Distort(DistortMethod.Perspective, distortSettings, arguments);
            output.BorderColor = backgroundColor;
            output.Border(2);
            output.ColorFuzz = ColorFuzz;
            output.Trim();
            output.RePage();
        }
        private void TrimImage(IMagickImage result)
        {
            if (!Trim)
            {
                return;
            }

            result.Trim();
            result.RePage();
        }
Example #4
0
        private MagickGeometry TrimImage(IMagickImage image, MagickColor backgroundColor, int borderSize)
        {
            image.BorderColor = backgroundColor;
            image.Border(borderSize);
            image.ColorFuzz = ColorFuzz;
            image.Trim();
            image.RePage();

            return(new MagickGeometry(image.Width, image.Height));
        }
Example #5
0
        private void ExecuteOuterTrim(IMagickImage image, MagickColor borderColor)
        {
            image.BackgroundColor = borderColor;
            image.ColorFuzz       = ColorFuzz;
            image.Trim();
            image.RePage();

            var geometry = new MagickGeometry(0, 0, image.Width, image.Height);

            ShiftGeometry(geometry);
            Crop(image, geometry);
        }
Example #6
0
        public IMagickImage Execute(IMagickImage image, ValueItemCollection values)
        {
            Variables = values;

            if (!CropVisible)
            {
                return(image);
            }

            var dw = image.Width / 1000.0;
            var dh = image.Height / 1000.0;

            image.Crop((int)(CropX * dw), (int)(CropY * dh), (int)(CropWidth * dw),
                       (int)(CropHeight * dh));
            image.RePage();
            return(image);
        }
Example #7
0
 private void Crop(IMagickImage image, MagickGeometry area)
 {
     ShiftGeometry(area);
     image.Crop(area.X, area.Y, area.Width, area.Height);
     image.RePage();
 }
Example #8
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");
        }