protected override RequestedAction LayoutImage(ImageState s) { //Only activated if both width and height are specified, and mode=crop. if (s.settings.Mode != FitMode.Crop || s.settings.Width < 0 || s.settings.Height < 0) { return(RequestedAction.None); } //Calculate bounding box for all coordinates specified. double[] focus = NameValueCollectionExtensions.GetList <double>(s.settings, "c.focus", null, 2, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72); if (focus == null) { return(RequestedAction.None); } RectangleF box = PolygonMath.GetBoundingBox(focus); var bounds = new RectangleF(new PointF(0, 0), s.originalSize); //Clip box to original image bounds box = PolygonMath.ClipRectangle(box, bounds); var targetSize = new SizeF(s.settings.Width, s.settings.Height); SizeF copySize; //Now, we can either crop as closely as possible or as loosely as possible. if (NameValueCollectionExtensions.Get <bool>(s.settings, "c.zoom", false) && box.Width > 0 && box.Height > 0) { //Crop close copySize = PolygonMath.ScaleOutside(box.Size, targetSize); } else { //Crop minimally copySize = PolygonMath.ScaleInside(targetSize, bounds.Size); //Ensure it's outside the box if (!PolygonMath.FitsInside(box.Size, copySize)) { copySize = PolygonMath.ScaleOutside(box.Size, copySize); } } //Clip to bounds. box = PolygonMath.ClipRectangle(PolygonMath.ExpandTo(box, copySize), bounds); s.copyRect = box; ///What is the vertical and horizontal aspect ratio different in result pixels? var padding = PolygonMath.ScaleInside(box.Size, targetSize); padding = new SizeF(targetSize.Width - padding.Width, targetSize.Height - padding.Height); //So, if we haven't met the aspect ratio yet, what mode will we pass on? var finalmode = NameValueCollectionExtensions.Get <FitMode>(s.settings, "c.finalmode", FitMode.Pad); //Crop off 1 or 2 pixels instead of padding without worrying too much if (finalmode == FitMode.Pad && padding.Width + padding.Height < 3) { finalmode = FitMode.Crop; } s.settings.Mode = finalmode; return(RequestedAction.None); }
protected bool LegacyDrawWatermark(ImageState s) { Graphics g = s.destGraphics; if (g == null) { return(false); } string watermark; if (!LegacyParseWatermark(s.settings, out watermark)) { return(false); } RectangleF imageBox = PolygonMath.GetBoundingBox(s.layout["image"]); //Floor and ceiling values to prevent fractional placement. imageBox.Width = (float)Math.Floor(imageBox.Width); imageBox.Height = (float)Math.Floor(imageBox.Height); imageBox.X = (float)Math.Ceiling(imageBox.X); imageBox.Y = (float)Math.Ceiling(imageBox.Y); SizeF watermarkSize = this.watermarkSize; SizeF topLeftPadding = this.topLeftPadding; SizeF bottomRightPadding = this.bottomRightPadding; //Load the file specified in the querystring, Bitmap wb = GetMemCachedBitmap(watermark); lock (wb) { //If percentages, resolve to pixels if (valuesPercentages) { //Force into 0..1 range, inclusive. watermarkSize.Height = Math.Max(0, Math.Min(1, watermarkSize.Height)); watermarkSize.Width = Math.Max(0, Math.Min(1, watermarkSize.Width)); topLeftPadding.Height = Math.Max(0, Math.Min(1, topLeftPadding.Height)); topLeftPadding.Width = Math.Max(0, Math.Min(1, topLeftPadding.Width)); bottomRightPadding.Height = Math.Max(0, Math.Min(1, bottomRightPadding.Height)); bottomRightPadding.Width = Math.Max(0, Math.Min(1, bottomRightPadding.Width)); //Make sure everything adds up to 1 double totalWidth = watermarkSize.Width + topLeftPadding.Width + bottomRightPadding.Width; if (totalWidth > 1) { totalWidth = 1 / totalWidth; //Turn it into the factor we have to multiple by to make everything fit. watermarkSize.Width *= (float)totalWidth; topLeftPadding.Width *= (float)totalWidth; bottomRightPadding.Width *= (float)totalWidth; } double totalHeight = watermarkSize.Height + topLeftPadding.Height + bottomRightPadding.Height; if (totalHeight > 1) { totalHeight = 1 / totalHeight; //Turn it into the factor we have to multiply by to make everything fit. watermarkSize.Height *= (float)totalHeight; topLeftPadding.Height *= (float)totalHeight; bottomRightPadding.Height *= (float)totalHeight; } //Now, we can resolve the percentages to pixels. watermarkSize.Height *= imageBox.Height; watermarkSize.Width *= imageBox.Width; topLeftPadding.Height *= imageBox.Height; topLeftPadding.Width *= imageBox.Width; bottomRightPadding.Height *= imageBox.Height; bottomRightPadding.Width *= imageBox.Width; } //Keep aspect ratio, shrinking further if needed. if (keepAspectRatio) { watermarkSize = PolygonMath.DownScaleInside(wb.Size, watermarkSize); } //Floor all values to avoid rounding errors and blurry lines. watermarkSize = new SizeF((float)Math.Floor(watermarkSize.Width), (float)Math.Floor(watermarkSize.Height)); topLeftPadding = new SizeF((float)Math.Floor(topLeftPadding.Width), (float)Math.Floor(topLeftPadding.Height)); bottomRightPadding = new SizeF((float)Math.Floor(bottomRightPadding.Width), (float)Math.Floor(bottomRightPadding.Height)); //Check boundingbox SizeF watermarkBoundingBox = new SizeF(watermarkSize.Width + topLeftPadding.Width + bottomRightPadding.Width, watermarkSize.Height + topLeftPadding.Height + bottomRightPadding.Height); //Don't draw the watermark if it is too small. if (!PolygonMath.FitsInside(watermarkBoundingBox, imageBox.Size)) { if (hideIfTooSmall) { return(true); } else { SizeF oldSize = watermarkBoundingBox; watermarkBoundingBox = PolygonMath.DownScaleInside(watermarkBoundingBox, imageBox.Size); watermarkSize.Width -= (oldSize.Width - watermarkBoundingBox.Width); watermarkSize.Height -= (oldSize.Height - watermarkBoundingBox.Height); } } //Floor all values again watermarkSize = new SizeF((float)Math.Floor(watermarkSize.Width), (float)Math.Floor(watermarkSize.Height)); topLeftPadding = new SizeF((float)Math.Floor(topLeftPadding.Width), (float)Math.Floor(topLeftPadding.Height)); bottomRightPadding = new SizeF((float)Math.Floor(bottomRightPadding.Width), (float)Math.Floor(bottomRightPadding.Height)); float innerWidth = (float)Math.Floor(imageBox.Width - Math.Abs(topLeftPadding.Width) - Math.Abs(bottomRightPadding.Width)); float innerHeight = (float)Math.Floor(imageBox.Height - Math.Abs(topLeftPadding.Height) - Math.Abs(bottomRightPadding.Height)); float x = 0; float y = 0; if (align == ContentAlignment.BottomCenter || align == ContentAlignment.BottomLeft || align == ContentAlignment.BottomRight) { y = (innerHeight - watermarkSize.Height) + topLeftPadding.Height; } if (align == ContentAlignment.MiddleCenter || align == ContentAlignment.MiddleLeft || align == ContentAlignment.MiddleRight) { y = (innerHeight - watermarkSize.Height) / 2 + topLeftPadding.Height; } if (align == ContentAlignment.TopCenter || align == ContentAlignment.TopLeft || align == ContentAlignment.TopRight) { y = topLeftPadding.Height; } if (align == ContentAlignment.BottomRight || align == ContentAlignment.MiddleRight || align == ContentAlignment.TopRight) { x = (innerWidth - watermarkSize.Width) + topLeftPadding.Width; } if (align == ContentAlignment.BottomCenter || align == ContentAlignment.MiddleCenter || align == ContentAlignment.TopCenter) { x = (innerWidth - watermarkSize.Width) / 2 + topLeftPadding.Width; } if (align == ContentAlignment.BottomLeft || align == ContentAlignment.MiddleLeft || align == ContentAlignment.TopLeft) { x = topLeftPadding.Width; } //Draw watermark g.DrawImage(wb, new Rectangle((int)(x + imageBox.X), (int)(y + imageBox.Y), (int)watermarkSize.Width, (int)watermarkSize.Height)); } return(true); }
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); }