Exemple #1
0
        protected virtual void AssertLayout(string sourceSize, string resizeSettings, Action <RectangleF> assertImage, Action <SizeF> assertCanvas)
        {
            var sourceSizeSettings = new ResizeSettings(sourceSize);
            var result             = _imageLayoutBuilder.BuildLayout(new Size(sourceSizeSettings.Width, sourceSizeSettings.Height), new ResizeSettings(resizeSettings));

            if (assertCanvas != null)
            {
                assertCanvas(result.CanvasSize);
            }
            if (assertImage != null)
            {
                assertImage(result.Image);
            }

            var maxWidth  = (int)(Math.Max(result.Image.Width, result.CanvasSize.Width));
            var maxHeight = (int)(Math.Max(result.Image.Height, result.CanvasSize.Height));

            var padding = (int)Math.Max(Math.Abs(result.Image.Y), Math.Abs(result.Image.X)) + 20;

            if ((maxWidth + padding) < 400)
            {
                padding = (400 - maxWidth) / 2;
            }

            // create a bitmap for visualizing
            var bitmapSize = new RectangleF(0, 0, maxWidth + padding * 2, maxHeight + (padding * 2));

            using (var bmp = new Bitmap((int)bitmapSize.Width, (int)bitmapSize.Height))
            {
                using (var gfx = Graphics.FromImage(bmp))
                {
                    // set the background
                    gfx.FillRectangle(new SolidBrush(Color.White), 0, 0, bmp.Width, bmp.Height);

                    // output the results
                    gfx.DrawString("Source: " + sourceSize, new Font("Thaoma", 8), Brushes.Black, new RectangleF(0, 0, bmp.Width, bmp.Height), new StringFormat {
                        Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Near
                    });
                    gfx.DrawString("Destination: " + resizeSettings, new Font("Thaoma", 8), Brushes.Black, new RectangleF(0, 0, bmp.Width, bmp.Height), new StringFormat {
                        Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Near
                    });
                    gfx.DrawString("Canvas: " + result.CanvasSize.Width + "x" + result.CanvasSize.Height, new Font("Thaoma", 8), Brushes.Green, new RectangleF(0, 0, bmp.Width, bmp.Height), new StringFormat {
                        Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Far
                    });
                    gfx.DrawString("Image: " + result.Image.Width + "x" + result.Image.Height, new Font("Thaoma", 8), Brushes.Red, new RectangleF(0, 0, bmp.Width, bmp.Height), new StringFormat {
                        Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Far
                    });

                    //PolygonMath.AlignWith()
                    var canvas = new RectangleF(padding, padding, result.CanvasSize.Width, result.CanvasSize.Height);
                    var image  = new RectangleF(padding + result.Image.X, padding + result.Image.Y, result.Image.Width, result.Image.Height);
                    var points = new List <PointF>();
                    points.AddRange(PolygonMath.ToPoly(canvas));
                    points.AddRange(PolygonMath.ToPoly(image));
                    points = PolygonMath.AlignWith(points.ToArray(), PolygonMath.ToPoly(bitmapSize), ContentAlignment.MiddleCenter).ToList();
                    canvas = PolygonMath.GetBoundingBox(points.Take(4).ToArray());
                    image  = PolygonMath.GetBoundingBox(points.Skip(4).Take(4).ToArray());
                    gfx.FillRectangle(new SolidBrush(Color.Green), canvas);
                    gfx.DrawRectangle(new Pen(Color.Red, 2), image.X, image.Y, image.Width, image.Height);
                }
                var fileName = sourceSize + "--" + resizeSettings + ".bmp";
                var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                }
                bmp.Save(filePath, ImageFormat.Bmp);

                Trace.WriteLine("Source:        " + sourceSize);
                Trace.WriteLine("Destination:   " + resizeSettings);
                Trace.WriteLine("   Result:     " + filePath);
            }
        }
        public void ApplySettings(ResizeSettings settings)
        {
            copyRect = determineManualCropWindow(settings);

            //Save the manual crop size.
            SizeF      manualCropSize = copyRect.Size;
            RectangleF manualCropRect = copyRect;

            FitMode fit = determineFitMode(settings);

            //Aspect ratio of the image
            double imageRatio = copyRect.Width / copyRect.Height;

            //Zoom factor
            double zoom = settings.Get <double>("zoom", 1);

            //The target size for the image
            targetSize = new SizeF(-1, -1);
            //Target area for the image
            areaSize = new SizeF(-1, -1);
            //If any dimensions are specified, calculate. Otherwise, use original image dimensions
            if (settings.Width != -1 || settings.Height != -1 || settings.MaxHeight != -1 || settings.MaxWidth != -1)
            {
                //A dimension was specified.
                //We first calculate the largest size the image can be under the width/height/maxwidth/maxheight restriction
                //- pretending stretch=fill and scale=both

                //Temp vars - results stored in targetSize and areaSize
                double width     = settings.Width;
                double height    = settings.Height;
                double maxwidth  = settings.MaxWidth;
                double maxheight = settings.MaxHeight;

                //Eliminate cases where both a value and a max value are specified: use the smaller value for the width/height
                if (maxwidth > 0 && width > 0)
                {
                    width = Math.Min(maxwidth, width); maxwidth = -1;
                }
                if (maxheight > 0 && height > 0)
                {
                    height = Math.Min(maxheight, height); maxheight = -1;
                }

                //Handle cases of width/maxheight and height/maxwidth as in legacy version
                if (width != -1 && maxheight != -1)
                {
                    maxheight = Math.Min(maxheight, (width / imageRatio));
                }
                if (height != -1 && maxwidth != -1)
                {
                    maxwidth = Math.Min(maxwidth, (height * imageRatio));
                }


                //Move max values to width/height. FitMode should already reflect the mode we are using, and we've already resolved mixed modes above.
                width  = Math.Max(width, maxwidth);
                height = Math.Max(height, maxheight);

                //Calculate missing value (a missing value is handled the same everywhere).
                if (width > 0 && height <= 0)
                {
                    height = width / imageRatio;
                }
                else if (height > 0 && width <= 0)
                {
                    width = height * imageRatio;
                }

                //We now have width & height, our target size. It will only be a different aspect ratio from the image if both 'width' and 'height' are specified.

                //FitMode.Max
                if (fit == FitMode.Max)
                {
                    areaSize = targetSize = PolygonMath.ScaleInside(manualCropSize, new SizeF((float)width, (float)height));
                    //FitMode.Pad
                }
                else if (fit == FitMode.Pad)
                {
                    areaSize   = new SizeF((float)width, (float)height);
                    targetSize = PolygonMath.ScaleInside(manualCropSize, areaSize);
                    //FitMode.crop
                }
                else if (fit == FitMode.Crop)
                {
                    //We autocrop - so both target and area match the requested size
                    areaSize = targetSize = new SizeF((float)width, (float)height);
                    RectangleF copyRect;

                    ScaleMode scale             = settings.Scale;
                    bool      cropWidthSmaller  = manualCropSize.Width <= (float)width;
                    bool      cropHeightSmaller = manualCropSize.Height <= (float)height;

                    //TODO: consider mode=crop;fit=upscale

                    // With both DownscaleOnly (where only one dimension is smaller than
                    // requested) and UpscaleCanvas, we will have a targetSize based on the
                    // minWidth & minHeight.
                    // TODO: what happens if mode=crop;scale=down but the target is larger than the source?

                    if ((scale == ScaleMode.DownscaleOnly && (cropWidthSmaller != cropHeightSmaller)) ||
                        (scale == ScaleMode.UpscaleCanvas && (cropHeightSmaller || cropWidthSmaller)))
                    {
                        var minWidth  = Math.Min(manualCropSize.Width, (float)width);
                        var minHeight = Math.Min(manualCropSize.Height, (float)height);

                        targetSize = new SizeF(minWidth, minHeight);

                        copyRect = manualCropRect = new RectangleF(0, 0, minWidth, minHeight);

                        // For DownscaleOnly, the areaSize is adjusted to the new targetSize as well.
                        if (scale == ScaleMode.DownscaleOnly)
                        {
                            areaSize = targetSize;
                        }
                    }
                    else
                    {
                        //Determine the size of the area we are copying
                        Size sourceSize = PolygonMath.RoundPoints(PolygonMath.ScaleInside(areaSize, manualCropSize));
                        //Center the portion we are copying within the manualCropSize
                        copyRect = new RectangleF(0, 0, sourceSize.Width, sourceSize.Height);
                    }


                    // Align the actual source-copy rectangle inside the available
                    // space based on the anchor.
                    this.copyRect = PolygonMath.ToRectangle(PolygonMath.AlignWith(copyRect, this.copyRect, settings.Anchor));
                }
                else
                { //Stretch and carve both act like stretching, so do that:
                    areaSize = targetSize = new SizeF((float)width, (float)height);
                }
            }
            else
            {
                //No dimensions specified, no fit mode needed. Use manual crop dimensions
                areaSize = targetSize = manualCropSize;
            }

            //Multiply both areaSize and targetSize by zoom.
            areaSize.Width    *= (float)zoom;
            areaSize.Height   *= (float)zoom;
            targetSize.Width  *= (float)zoom;
            targetSize.Height *= (float)zoom;

            //Todo: automatic crop is permitted to break the scaling rule Fix!!

            //Now do upscale/downscale check If they take effect, set targetSize to imageSize
            if (settings.Scale == ScaleMode.DownscaleOnly)
            {
                if (PolygonMath.FitsInside(manualCropSize, targetSize))
                {
                    //The image is smaller or equal to its target polygon. Use original image coordinates instead.
                    areaSize = targetSize = manualCropSize;
                    copyRect = manualCropRect;
                }
            }
            else if (settings.Scale == ScaleMode.UpscaleOnly)
            {
                if (!PolygonMath.FitsInside(manualCropSize, targetSize))
                {
                    //The image is larger than its target. Use original image coordinates instead
                    areaSize = targetSize = manualCropSize;
                    copyRect = manualCropRect;
                }
            }
            else if (settings.Scale == ScaleMode.UpscaleCanvas)
            {
                //Same as downscaleonly, except areaSize isn't changed.
                if (PolygonMath.FitsInside(manualCropSize, targetSize))
                {
                    //The image is smaller or equal to its target polygon.

                    //Use manual copy rect/size instead.

                    targetSize = manualCropSize;
                    copyRect   = manualCropRect;
                }
            }


            //May 12: require max dimension and round values to minimize rounding differences later.
            areaSize.Width    = Math.Max(1, (float)Math.Round(areaSize.Width));
            areaSize.Height   = Math.Max(1, (float)Math.Round(areaSize.Height));
            targetSize.Width  = Math.Max(1, (float)Math.Round(targetSize.Width));
            targetSize.Height = Math.Max(1, (float)Math.Round(targetSize.Height));
        }
Exemple #3
0
        internal RectangleF GetCrop()
        {
            if (Zoom)
            {
                return(GetZoomCrop());
            }


            var salientBounds = SalientBoundingBox();

            var idealCropSize = PolygonMath.ScaleInside(TargetSize, ImageSize);

            var fits = PolygonMath.FitsInside(salientBounds.Size, idealCropSize);

            // If there's no need to crop out a salient region, then center on the salient bounding box and align within the image bounds.
            if (fits)
            {
                return(PolygonMath.AlignWithin(new RectangleF(PointF.Empty, idealCropSize),
                                               new RectangleF(PointF.Empty, ImageSize), PolygonMath.Midpoint(salientBounds)));
            }

            if (NeverCropSalientArea)
            {
                var compromiseCrop = new SizeF(Math.Max(salientBounds.Width, idealCropSize.Width),
                                               Math.Max(salientBounds.Height, idealCropSize.Height));

                // Don't worry about 3 pixels or less, that's just annoying.
                if (compromiseCrop.Width - idealCropSize.Width <= 3 &&
                    compromiseCrop.Height - idealCropSize.Height <= 3)
                {
                    compromiseCrop = idealCropSize;
                }

                return(PolygonMath.AlignWithin(new RectangleF(PointF.Empty, compromiseCrop),
                                               new RectangleF(PointF.Empty, ImageSize), PolygonMath.Midpoint(salientBounds)));
            }

            // Find the least bad crop (brute force)
            var vertical = salientBounds.Height > idealCropSize.Height;

            var choiceCount = vertical
                ? salientBounds.Height - idealCropSize.Height
                : salientBounds.Width - idealCropSize.Width;

            var searchArea = new RectangleF(vertical ? 0 : salientBounds.X, vertical ? salientBounds.Y : 0, vertical ? idealCropSize.Width : salientBounds.Width, vertical ? salientBounds.Height : idealCropSize.Height);

            var initialWindow = PolygonMath.AlignWith(new RectangleF(PointF.Empty, idealCropSize), searchArea,
                                                      ContentAlignment.TopLeft);

            double bestVolume = 0;
            var    bestCrop   = initialWindow;

            for (var i = 0; i < choiceCount; i++)
            {
                var window = initialWindow;
                window.Offset(vertical ? 0 : i, vertical ? i : 0);

                var volume = SalientArea.IntersectVolume(window, Regions);
                if (volume > bestVolume)
                {
                    bestVolume = volume;
                    bestCrop   = window;
                }
            }
            return(bestCrop);
        }