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); }
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)); }
public override void RenderTo(Resizing.ImageState s) { if (string.IsNullOrEmpty(Text)) { return; } string finalText = Text; if (finalText.IndexOf('#') > -1) { Regex r = new Regex("\\#\\{([^}]+)\\}"); finalText = r.Replace(finalText, delegate(Match m){ string val = s.settings[m.Groups[1].Value]; if (val == null) { return(""); } else { return(val); } }); } SizeF naturalSize = SizeF.Empty; SizeF unrotatedSize = SizeF.Empty; RectangleF bounds = this.CalculateLayerCoordinates(s, delegate(double maxwidth, double maxheight) { using (Font f = GetFont()) using (StringFormat sf = GetFormat()){ naturalSize = s.destGraphics.MeasureString(finalText, f, new PointF(), sf); SizeF size = naturalSize; unrotatedSize = Fill ? PolygonMath.ScaleInside(size, new SizeF((float)maxwidth, (float)maxheight)) : size; if (Angle != 0) { size = PolygonMath.GetBoundingBox(PolygonMath.RotatePoly(PolygonMath.ToPoly(new RectangleF(new PointF(0, 0), size)), Angle)).Size; } if (Fill) { size = PolygonMath.ScaleInside(size, new SizeF((float)maxwidth, (float)maxheight)); } f.FontFamily.Dispose(); return(PolygonMath.RoundPoints(size)); } }, true); using (Font f = GetFont()) { s.destGraphics.SmoothingMode = SmoothingMode.HighQuality; s.destGraphics.TextRenderingHint = Rendering; // Utils.parseEnum<TextRenderingHint>(s.settings["watermark.rendering"], this.Rendering); ; s.destGraphics.PixelOffsetMode = PixelOffsetMode.HighQuality; s.destGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic; s.destGraphics.CompositingMode = CompositingMode.SourceOver; s.destGraphics.CompositingQuality = CompositingQuality.HighQuality; s.destGraphics.ResetTransform(); if (Angle != 0) { s.destGraphics.RotateTransform((float)Angle); } s.destGraphics.ScaleTransform(unrotatedSize.Width / naturalSize.Width, unrotatedSize.Height / naturalSize.Height); s.destGraphics.TranslateTransform(bounds.X, bounds.Y, MatrixOrder.Append); using (StringFormat sf = GetFormat()) { DrawString(s.destGraphics, finalText, f, new Point(0, 0), sf); } s.destGraphics.ResetTransform(); f.FontFamily.Dispose(); } }
private Size GetOutputSize(ResizeSettings settings, double boundingWidth, double boundingHeight) { // Output size is determined by resize settings, if available. // maxwidth, maxheight // – Fit the image within the specified bounds, preserving aspect ratio. // width, height // – Force the final width and/or height to certain dimensions. // Whitespace will be added if the aspect ratio is different. // This plugin renders to a size within the requested size and then expects remaining plugins in the // pipeline to perform and additional processing such as adding whitespace, etc. // It can safely treat width/height as maxwidth/maxheight. double imageRatio = boundingWidth / boundingHeight; double width = settings.Width; double height = settings.Height; double maxwidth = settings.MaxWidth; double maxheight = settings.MaxHeight; //Allow overrides with pdfwidth and pdfheight when we *want* to rescale afterwards. int pw = settings.Get("pdfwidth", -1); int ph = settings.Get("pdfheight", -1); if (pw > 0) { width = pw; maxwidth = -1; } if (ph > 0) { height = ph; maxheight = -1; } //Handle cases of width/maxheight and height/maxwidth as in legacy versions. if (width != -1 && maxheight != -1) { maxheight = Math.Min(maxheight, (width / imageRatio)); } if (height != -1 && maxwidth != -1) { maxwidth = Math.Min(maxwidth, (height * imageRatio)); } //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; } //Move values to width/height if (width <= 0) { width = maxwidth; } if (height <= 0) { height = maxheight; } //Calculate missing value(s) if (width > 0 && height <= 0) { height = width / imageRatio; } else if (height > 0 && width <= 0) { width = height * imageRatio; } else if (width <= 0 && height <= 0) { // If neither width nor height as specified use default values width = DefaultWidth; height = DefaultHeight; } // Limit maximum output size width = Math.Min(width, this.MaxWidth); height = Math.Min(height, this.MaxHeight); // Determine the scaling values, and use the smallest to ensure we fit in the bounding box without changing // the aspect ratio otherwise we will crop. //Use a scaled version of boundingBox inside our maximum width and height constraints. return(PolygonMath.RoundPoints(PolygonMath.ScaleInside(new SizeF((float)boundingWidth, (float)boundingHeight), new SizeF((float)width, (float)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); }