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)); }
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); }