public RectangleF getCustomCropSourceRect(SizeF imageSize) { double xunits = this.Get <double>("cropxunits", 0); double yunits = this.Get <double>("cropyunits", 0); return(PolygonMath.GetCroppingRectangle(CropValues, xunits, yunits, imageSize)); }
/// <summary> /// Draws a gradient around the specified polygon. Fades from 'inner' to 'outer' over a distance of 'width' pixels. /// </summary> /// <param name="g"></param> /// <param name="poly"></param> /// <param name="inner"></param> /// <param name="outer"></param> /// <param name="width"></param> public static void DrawOuterGradient(Graphics g, PointF[] poly, Color inner, Color outer, float width) { PointF[,] corners = PolygonMath.GetCorners(poly, width); PointF[,] sides = PolygonMath.GetSides(poly, width); //Overlapping these causes darker areas... Don't use InflatePoly //Paint corners for (int i = 0; i <= corners.GetUpperBound(0); i++) { PointF[] pts = PolygonMath.GetSubArray(corners, i); using (Brush b = PolygonMath.GenerateRadialBrush(inner, outer, pts[0], width + 1)) { g.FillPolygon(b, pts); } } //Paint sides for (int i = 0; i <= sides.GetUpperBound(0); i++) { PointF[] pts = PolygonMath.GetSubArray(sides, i); using (LinearGradientBrush b = new LinearGradientBrush(pts[3], pts[0], inner, outer)) { b.SetSigmaBellShape(1); b.WrapMode = WrapMode.TileFlipXY; g.FillPolygon(b, pts); } } }
/// <summary> /// Detects features on a grayscale image. /// </summary> /// <param name="img"></param> /// <param name="storage"></param> /// <returns></returns> protected override List <Face> DetectFeatures(IplImage img, CvMemStorage storage) { //Determine minimum face size var minSize = (int)Math.Round((double)MinSizePercent / 100.0 * Math.Min(img.Width, img.Height)); //Detect faces (frontal). TODO: side Stopwatch watch = Stopwatch.StartNew(); CvAvgComp[] faces = Cv.HaarDetectObjects(img, Cascades["FaceCascade"], storage, 1.0850, MinConfidenceLevel, 0, new CvSize(minSize, minSize)).ToArrayAndDispose(); watch.Stop(); Debug.WriteLine("Face detection time = " + watch.ElapsedMilliseconds); //Sort by accuracy Array.Sort <CvAvgComp>(faces, CompareByNeighbors); //Convert into feature objects list List <Face> features = new List <Face>(faces.Length); foreach (CvAvgComp face in faces) { features.Add(new Face(PolygonMath.ScaleRect(face.Rect.ToRectangleF(), ExpandX, ExpandY), face.Neighbors)); } //Unless we're below MinFaces, filter out the low confidence matches. while (features.Count > MinFaces && features[features.Count - 1].Accuracy < ConfidenceLevelThreshold) { features.RemoveAt(features.Count - 1); } //Never return more than [MaxFaces] return((features.Count > MaxFaces) ? features.GetRange(0, MaxFaces) : features); }
/// <summary> /// When the player painted a polygon, check if this collectable is inside, /// collect and destroy it and tell it to the polygonpainter to handle it. /// </summary> /// <param name="e">E.</param> void polygonPainter_PolygonPainted(PolygonPainterEventArgs e) { if (PolygonMath.IntersectNew(e.Points, e.PointCount, transform.position)) { DestroyMe(); } }
/// <summary> /// Translates and scales all rings and invisible polygons as specified. /// </summary> /// <param name="from"></param> /// <param name="to"></param> public void Shift(RectangleF from, RectangleF to) { PointF fromOrigin = new PointF(from.X + (from.Width / 2), from.Y + (from.Height / 2)); PointF toOrigin = new PointF(to.X + (to.Width / 2), to.Y + (to.Height / 2)); PointF offset = new PointF(toOrigin.X - fromOrigin.X, toOrigin.Y - fromOrigin.Y); double xd = to.Width / from.Width; double yd = to.Height / from.Height; //Offset points foreach (PointSet ps in ringList) { if (ps.flags != PointFlags.Ignored) { ps.points = PolygonMath.MovePoly(ps.points, offset); } } //Scale points foreach (PointSet ps in ringList) { if (ps.flags != PointFlags.Ignored) { ps.points = PolygonMath.ScalePoints(ps.points, xd, yd, toOrigin); } } }
/// <summary> /// Translates the specified points from the source bitmap coordinate space to the destination image coordinate space. /// </summary> /// <param name="s"></param> /// <returns></returns> public PointF[] TranslatePoints(PointF[] points, ImageState s) { PointF[] moved = new PointF[points.Length]; PointF[] dest = s.layout["image"]; double newWidth = PolygonMath.Dist(dest[0], dest[1]); double newHeight = PolygonMath.Dist(dest[1], dest[2]); double xScale = newWidth / s.copyRect.Width; double yScale = newHeight / s.copyRect.Height; double angle = GetAngleFromXAxis(dest[0], dest[1]); for (int i = 0; i < points.Length; i++) { PointF p = points[i]; p.X -= s.copyRect.X; //Translate p.Y -= s.copyRect.Y; p.X = (float)(p.X * xScale); //Scale p.Y = (float)(p.Y * yScale); p = PolygonMath.RotateVector(p, angle, dest[0]); //Rotate p.X += dest[0].X; p.Y += dest[1].Y; moved[i] = p; } return(moved); }
protected override RequestedAction RenderEffects(ImageState s) { if (base.RenderEffects(s) == RequestedAction.Cancel) { return(RequestedAction.Cancel); //Call extensions } if (s.destGraphics == null) { return(RequestedAction.None); } //parse shadow Color shadowColor = ParseUtils.ParseColor(s.settings["shadowColor"], Color.Transparent); int shadowWidth = s.settings.Get <int>("shadowWidth", -1); //Skip on transparent or 0-width shadow if (shadowColor == Color.Transparent || shadowWidth <= 0) { return(RequestedAction.None); } using (Brush b = new SolidBrush(shadowColor)) { //Offsets may show inside the shadow - so we have to fix that s.destGraphics.FillPolygon(b, PolygonMath.InflatePoly(s.layout["shadowInner"], 1)); //Inflate 1 for FillPolygon rounding errors. } //Then we can draw the outer gradient DrawOuterGradient(s.destGraphics, s.layout["shadowInner"], shadowColor, Color.Transparent, shadowWidth); return(RequestedAction.None); }
/// <summary> /// Returns a rectangle representing the given ring - if it is an axis-parallel rectangle. Otherwise returns null; /// </summary> /// <param name="name"></param> /// <returns></returns> public RectangleF?GetRingAsRectF(string name) { var poly = this[name]; var rect = PolygonMath.GetBoundingBox(poly); var poly2 = PolygonMath.ToPoly(rect); return(PolygonMath.ArraysEqual(poly, poly2) ? (RectangleF?)rect : null); }
/// <summary> /// Rotates all existing rings (Except those flagged ignore) /// </summary> /// <param name="degrees"></param> /// <param name="origin"></param> /// <returns></returns> public void Rotate(double degrees, PointF origin) { foreach (PointSet ps in ringList) { if (ps.flags != PointFlags.Ignored) { ps.points = PolygonMath.RotatePoly(ps.points, degrees, origin); } } }
public void Round() { foreach (PointSet ps in ringList) { if (ps.flags != PointFlags.Ignored) { ps.points = PolygonMath.RoundPoints(ps.points); } } }
/// <summary> /// Scales all rings and invisible polygons by the specified factor, around the specified point. /// </summary> /// <param name="factor"></param> /// <param name="origin"></param> public void Scale(double factor, PointF origin) { foreach (PointSet ps in ringList) { if (ps.flags != PointFlags.Ignored) { ps.points = PolygonMath.ScalePoints(ps.points, factor, factor, origin); } } }
public static Coord?HitTestPolygon(PointT point, Coord radius, IEnumerable <PointT> points, IList <int> divisions, out PointT projected) { if (PolygonMath.GetWindingNumber(points, point) != 0) { projected = point; return(-1); } else { return(HitTestPolyline(point, radius, points, divisions, out projected)); } }
/// <summary> /// Gets a bounding box that encloses all rings that don't have ExcludeFromBoundingBox set. /// </summary> /// <returns></returns> public RectangleF GetBoundingBox() { List <PointF> points = new List <PointF>(ring.Count * 5); foreach (PointSet val in ringList) { if (val.flags == PointFlags.Ring) { points.AddRange(val.points); } } return(PolygonMath.GetBoundingBox(points.ToArray())); }
public override object HitTest(PointT point, Coord radius, out PointT projected) { projected = point; if (!BBox.Inflated(radius, radius).Contains(point)) { return(null); } if (PolygonMath.GetWindingNumber(Points, point) != 0) { projected = point; return(-1); } return(HitTestPolyline(point, radius, Points, Divisions, out projected)); }
/// <summary> /// Copies layout information from the given image state to the current instance. /// Does not populate message or 'features' variables /// </summary> /// <param name="s"></param> public void PopulateFrom(ImageState s) { this.ow = s.originalSize.Width; this.oh = s.originalSize.Height; this.cropx = s.copyRect.X; this.cropy = s.copyRect.Y; this.cropw = s.copyRect.Width; this.croph = s.copyRect.Height; RectangleF dest = PolygonMath.GetBoundingBox(s.layout["image"]); this.dx = dest.X; this.dy = dest.Y; this.dw = dest.Width; this.dh = dest.Height; }
RectangleF GetZoomCrop() { RectangleF box = SalientBoundingBox(); var bounds = new RectangleF(new PointF(0, 0), ImageSize); //Clip box to original image bounds box = PolygonMath.ClipRectangle(box, bounds); //Crop close var copySize = PolygonMath.ScaleOutside(box.Size, TargetSize); //Clip to bounds. return(PolygonMath.ClipRectangle(PolygonMath.ExpandTo(box, copySize), bounds)); }
/// <summary> /// Normalizes all rings and invisible polygons so that the outermost ring's bounding box starts at the specified orign. /// </summary> /// <param name="origin"></param> /// <returns></returns> public void Normalize(PointF origin) { PointF offset = GetBoundingBox().Location; offset.X *= -1; offset.Y *= -1; foreach (PointSet ps in ringList) { if (ps.flags != PointFlags.Ignored) { ps.points = PolygonMath.MovePoly(ps.points, offset); } } }
protected override RequestedAction RenderOverlays(ImageState s) { IEnumerable <Overlay> overlays = RequestOverlays; if (overlays == null) { return(RequestedAction.None); //skip town if there's no overlay object cached. } Graphics g = s.destGraphics; g.SmoothingMode = this.Smoothing; g.CompositingMode = CompositingMode.SourceOver; g.CompositingQuality = this.Compositing; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.PixelOffsetMode = PixelOffsetMode.HighQuality; foreach (Overlay o in overlays) { if (string.IsNullOrEmpty(o.OverlayPath)) { continue; //Skip overlays without a path } try { //Get the memcached overlay (assuming SourceFileCache is installed) using (Bitmap b = c.CurrentImageBuilder.LoadImage(o.OverlayPath, new ResizeSettings("memcache=true"))) using (ImageAttributes ia = new ImageAttributes()) { ia.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY); g.DrawImage(b, PolygonMath.getParallelogram(new LayoutEngine().GetOverlayParalellogram(o, b.Size, s)), new Rectangle(0, 0, b.Width, b.Height), GraphicsUnit.Pixel, ia); //Draw the poly if requested. if (Utils.getBool(s.settings, "customoverlay.showpoly", false)) { g.DrawPolygon(Pens.Green, new LayoutEngine().TranslatePoints(o.Poly, s)); } } } catch (FileNotFoundException fe) { if (!IgnoreMissingFiles) { throw new ImageMissingException("The overlay image " + o.OverlayPath + " could not be found."); } } } return(RequestedAction.None); }
protected override RequestedAction PreFlushChanges(ImageState s) { if (s.destGraphics == null) { return(RequestedAction.None); } Interlocked.Increment(ref requestCount); //Track request count string mode = c.get("trial.watermarkMode", "After500"); TrialWatermarkMode m = TrialWatermarkMode.After500; if ("always".Equals(mode, StringComparison.OrdinalIgnoreCase)) { m = TrialWatermarkMode.Always; } if ("randomly".Equals(mode, StringComparison.OrdinalIgnoreCase)) { m = TrialWatermarkMode.Randomly; } bool applyWatermark = (m == TrialWatermarkMode.Always); if (m == TrialWatermarkMode.After500 && requestCount > 500) { applyWatermark = true; } if (m == TrialWatermarkMode.Randomly) { applyWatermark = (new Random(requestCount).Next(0, 41) < 10); //25% chance } if (!applyWatermark) { return(RequestedAction.None); } DrawString(PolygonMath.GetBoundingBox(s.layout["image"]), s.destGraphics, "Unlicensed", FontFamily.GenericSansSerif, Color.FromArgb(70, Color.White)); return(RequestedAction.None); }
protected override RequestedAction LayoutEffects(ImageState s) { float shadowWidth = s.settings.Get <float>("shadowWidth", 0); if (shadowWidth != 0) { var offset = s.settings.GetList <float>("shadowOffset", 0, 2); PointF shadowOffset = offset == null ? new PointF(0, 0) : new PointF(offset[0], offset[1]); //Clone last ring, then offset it - provides the inner bounds of the shadow later s.layout.AddInvisiblePolygon("shadowInner", PolygonMath.MovePoly(s.layout.LastRing.points, shadowOffset)); //Determine the outer bound of the shadow s.layout.AddRing("shadow", PolygonMath.InflatePoly(s.layout.LastRing.points, new float[] { Math.Max(0, shadowWidth - shadowOffset.Y), Math.Max(0, shadowWidth + shadowOffset.X), Math.Max(0, shadowWidth + shadowOffset.Y), Math.Max(0, shadowWidth - shadowOffset.X) })); } return(RequestedAction.None); }
public PolyRect(PointF[] points) { var rect = PolygonMath.GetBoundingBox(points); this.SetBounds(rect.X, rect.Y, rect.Width, rect.Height); // PolygonMath.IsUnrotated can tell us that the points follow a // particular pattern, but in order to represent 90-degree rotations // and flips, we want to consider them to be *non-rectangle* points. // Therefore, we use a much more strict definition: there can only // be 4 points, and they must be in the canonical order. var right = this.x + this.width; var bottom = this.y + this.height; this.rect = points.Length == 4 && points[0].X == this.x && points[0].Y == this.y && points[1].X == right && points[1].Y == this.y && points[2].X == right && points[2].Y == bottom && points[3].X == this.x && points[3].Y == bottom; this.points = points.Select(p => new float[] { p.X, p.Y }).ToArray(); }
// To recognize a scribble we require the simplified line to reverse // direction at least three times. There are separate criteria for // erasing a shape currently being drawn and for erasing existing // shapes. // // The key difference between an "erase scribble" and a "cancel // scribble" is that an erase scribble starts out as such, while // a cancel scribble indicates that the user changed his mind, so // the line will not appear to be a scribble at the beginning. // The difference is detected by timestamps. For example, the // following diagram represents an "erase" operation and a "cancel" // operation. Assume the input points are evenly spaced in time, // and that the dots represent points where the input reversed // direction. // // Input points .......................... // Reversals (erase) . . . . . . // Reversals (cancel) . . . . // // So, a scribble is considered an erasure if it satisfies t0 < t1, // where t0 is the time between mouse-down and the first reversal, // and t1 is the time between the first and third reversals. A cancel // operation satisfies t0 > t1 instead. // // Both kinds of scribble need to satisfy the formula LL*c > CHA, // where c is a constant factor in pixels, LL is the drawn line // length and CHA is the area of the Convex Hull that outlines the // drawn figure. This formula basically detects that the user // is convering the same ground repeatedly; if the pen reverses // direction repeatedly but goes to new places each time, it's not // considered an erasure scribble. For a cancel scribble, LL is // computed starting from the first reversal. IEnumerable <Shape> RecognizeScribbleForEraseOrCancel(DragState state, out bool cancel, out List <PointT> simplifiedSS) { cancel = false; simplifiedSS = null; var tolerance = state._inputTransform.Transform(new VectorT(0, 10)).Length(); var simplifiedMP = LineMath.SimplifyPolyline( state.UnfilteredMousePoints.Select(p => p.Point), tolerance); List <int> reversals = FindReversals(simplifiedMP, 3); if (reversals.Count >= 3) { simplifiedSS = simplifiedMP.Select(p => state._inputTransform.Transform(p)).ToList(); // 3 reversals confirmed. Now decide: erase or cancel? int[] timeStampsMs = FindTimeStamps(state.UnfilteredMousePoints, simplifiedMP); int t0 = timeStampsMs[reversals[0]], t1 = timeStampsMs[reversals[2]] - t0; cancel = t0 > t1 + 500; // Now test the formula LL*c > CHA as explained above IListSource <PointT> simplifiedMP_ = cancel ? simplifiedMP.Slice(reversals[0]) : simplifiedMP.AsListSource(); float LL = simplifiedMP_.AdjacentPairs().Sum(pair => pair.A.Sub(pair.B).Length()); var hull = PointMath.ComputeConvexHull(simplifiedMP); float CHA = PolygonMath.PolygonArea(hull); if (LL * EraseNubWidth > CHA) { // Erasure confirmed. if (cancel) { return(EmptyList <Shape> .Value); } // Figure out which shapes to erase. To do this, we compute for // each shape the amount of the scribble that overlaps that shape. var simplifiedSS_ = simplifiedSS; return(_doc.Shapes.Where(s => ShouldErase(s, simplifiedSS_)).ToList()); } } return(null); }
private RectangleF determineManualCropWindow(ResizeSettings settings) { RectangleF cropWindow = copyRect; if (cropWindow.IsEmpty) { //Use the crop size if present. cropWindow = new RectangleF(new PointF(0, 0), originalSize); if (settings.GetList <double>("crop", 0, 4) != null) { cropWindow = PolygonMath.ToRectangle(settings.getCustomCropSourceRect(originalSize)); //Round the custom crop rectangle coordinates //Ensure right/bottom bounded after rounding completes cropWindow.Width = Math.Min(originalSize.Width - cropWindow.Left, cropWindow.Width); cropWindow.Height = Math.Min(originalSize.Height - cropWindow.Top, cropWindow.Height); if (cropWindow.Size.IsEmpty) { throw new Exception("You must specify a custom crop rectangle if crop=custom"); } } } return(cropWindow); }
/// <summary>Adds a point to DragState with a filtering algorithm. The /// default filtering algorithm supports "backing up the mouse" for erasure.</summary> /// <returns>true if a point was added, false if not.</returns> protected virtual bool AddFiltered(DragState state, DragPoint dp) { var points = state.MousePoints; if (points.Count < 2) { return(AddIfFarEnough(points, dp)); } var newSeg = (LineSegment <float>)points.Last.Point.To(dp.Point); // Strategy: // 1. Stroke the new segment with a simple rectangle with no endcap. // The rectangle will be a thin box around the point (halfwidth // is 1..2) var newRect = SimpleStroke(newSeg, EraseThreshold1); var newRectBB = newRect.ToBoundingBox(); // 2. Identify the most recent intersection point between this rectangle // (newRect) and the line being drawn. (if there is no such point, // there is no erasure. Done.) // 2b. That intersection point is the one _entering_ the rectangle. Find // the previous intersection point, the one that exits the rectangle. // this is the beginning of the region to potentially erase. var older = points.Reverse().AdjacentPairs().Select(pair => pair.B.Point.To(pair.A.Point)); Point <float> beginning = default(Point <float>); bool keepLooking = false; int offs = 0; var e = older.GetEnumerator(); for (; e.MoveNext(); offs++) { var seg = e.Current; var list = FindIntersectionsWith(seg, newRect, true).ToList(); if (list.Count != 0) { var min = list.MinOrDefault(p => p.A); beginning = min.B; if (!(offs == 0 && min.A == 1)) { if (keepLooking || !PolygonMath.IsPointInPolygon(newRect, seg.A)) { break; } keepLooking = true; } } else if (offs == 0) { } // todo: use IsPointInPolygon if itscs unstable } int iFirst = points.Count - 1 - offs; // index of the first point inside the region (iFirst-1 is outside) if (iFirst > 0) { // 3. Between here and there, identify the farthest point away from the // new point (dp.Point). var region = ((IList <DragPoint>)points).Slice(iFirst); int offsFarthest = region.IndexOfMax(p => (p.Point.Sub(dp.Point)).Quadrance()); int iFarthest = iFirst + offsFarthest; // 4. Make sure that all the points between here and there are close to // this line (within, say... 8 pixels). If so, we have erasure. var seg = dp.Point.To(points[iFarthest].Point); if (region.All(p => p.Point.DistanceTo(seg) < EraseThreshold2)) { // 5. Respond to erasure by deleting all the points between there // and here, not including the first or last point. // 5b. Consider adding the intersection point found in step 2b to // the point list, before adding the new point. points.Resize(iFirst); if (points.Count == 0 || (points.Last.Point.Sub(beginning)).Length() >= MinDistBetweenDragPoints) { points.Add(new DragPoint(beginning, 10, points)); } } } return(AddIfFarEnough(points, dp)); }
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)))); }
protected override RequestedAction RenderImage(ImageState s) { //Skip this when we are doing simulations if (s.destGraphics == null) { return(RequestedAction.None); } //If there's pre-rendering involved this optimization is utterly pointless. if (s.preRenderBitmap != null) { return(RequestedAction.None); } //Find out what the speed setting is. int speed = 0; if (string.IsNullOrEmpty(s.settings["speed"]) || !int.TryParse(s.settings["speed"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out speed)) { speed = 0; } if (speed < 1) { return(RequestedAction.None); } s.destGraphics.CompositingMode = CompositingMode.SourceCopy; s.destGraphics.CompositingQuality = CompositingQuality.HighSpeed; if (speed == 1) { s.destGraphics.InterpolationMode = InterpolationMode.HighQualityBilinear; } else { s.destGraphics.InterpolationMode = InterpolationMode.Bilinear; } s.destGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed; s.destGraphics.SmoothingMode = SmoothingMode.HighSpeed; s.copyAttibutes.SetWrapMode(WrapMode.TileFlipXY); if (speed < 3) { s.destGraphics.DrawImage(s.sourceBitmap, PolygonMath.getParallelogram(s.layout["image"]), s.copyRect, GraphicsUnit.Pixel, s.copyAttibutes); } else if (speed < 4) { Rectangle midsize = PolygonMath.ToRectangle(PolygonMath.GetBoundingBox(s.layout["image"])); using (Image thumb = s.sourceBitmap.GetThumbnailImage(midsize.Width, midsize.Height, delegate() { return(false); }, IntPtr.Zero)) { double xfactor = (double)thumb.Width / (double)s.sourceBitmap.Width; double yfactor = (double)thumb.Height / (double)s.sourceBitmap.Height; RectangleF copyPart = new RectangleF((float)(s.copyRect.Left * xfactor), (float)(s.copyRect.Top * yfactor), (float)(s.copyRect.Width * xfactor), (float)(s.copyRect.Height * yfactor)); if (Math.Floor(copyPart.Height) == thumb.Height || Math.Ceiling(copyPart.Height) == thumb.Height) { copyPart.Height = thumb.Height; } if (Math.Floor(copyPart.Width) == thumb.Width || Math.Ceiling(copyPart.Width) == thumb.Width) { copyPart.Width = thumb.Width; } s.destGraphics.DrawImage(thumb, PolygonMath.getParallelogram(s.layout["image"]), copyPart, GraphicsUnit.Pixel, s.copyAttibutes); } } else { RectangleF box = PolygonMath.GetBoundingBox(PolygonMath.getParallelogram(s.layout["image"])); s.destGraphics.CompositingMode = CompositingMode.SourceCopy; s.destGraphics.DrawImage(s.sourceBitmap, box.Left, box.Top, box.Width, box.Height); } return(RequestedAction.Cancel); }
/// <summary> /// Inflates the last ring using the specified padding options. Returns the resulting ring object /// </summary> /// <param name="name"></param> /// <param name="padding"></param> /// <returns></returns> public PointSet AddRing(string name, BoxPadding padding) { return(AddRing(name, PolygonMath.InflatePoly(LastRing.points, padding.GetEdgeOffsets()))); }
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 override RequestedAction PreRenderImage(ImageState s) { //Skip this when we are doing simulations if (s.destGraphics == null) { return(RequestedAction.None); } string sf = s.settings["fi.scale"]; if (string.IsNullOrEmpty(sf)) { return(RequestedAction.None); } bool validAlg = false; FREE_IMAGE_FILTER filter = ParseResizeAlgorithm(sf, FREE_IMAGE_FILTER.FILTER_CATMULLROM, out validAlg); if (!validAlg) { throw new ImageProcessingException("The specified resizing filter '" + sf + "' did not match bicubic, bilinear, box, bspline, catmullrom, or lanczos."); } //Set copy attributes s.copyAttibutes.SetWrapMode(WrapMode.TileFlipXY); //The minimum dimensions of the temporary bitmap. SizeF targetSize = PolygonMath.getParallelogramSize(s.layout["image"]); targetSize = new SizeF((float)Math.Ceiling(targetSize.Width), (float)Math.Ceiling(targetSize.Height)); s.ApplyCropping(); s.EnsurePreRenderBitmap(); //The size of the temporary bitmap. //We want it larger than the size we'll use on the final copy, so we never upscale it //- but we also want it as small as possible so processing is fast. SizeF tempSize = PolygonMath.ScaleOutside(targetSize, s.copyRect.Size); int tempWidth = (int)Math.Ceiling(tempSize.Width); int tempHeight = (int)Math.Ceiling(tempSize.Height); FIBITMAP src = FIBITMAP.Zero; FIBITMAP midway = FIBITMAP.Zero; try { var oldbit = s.preRenderBitmap ?? s.sourceBitmap; //Crop if needed, Convert, scale, then convert back. src = FreeImage.CreateFromBitmap(oldbit); midway = FreeImage.Rescale(src, tempWidth, tempHeight, filter); FreeImage.UnloadEx(ref src); //Clear the old pre-rendered image if needed if (s.preRenderBitmap != null) { s.preRenderBitmap.Dispose(); } //Reassign the pre-rendered image s.preRenderBitmap = FreeImage.GetBitmap(midway); s.copyRect = new RectangleF(0, 0, s.preRenderBitmap.Width, s.preRenderBitmap.Height); FreeImage.UnloadEx(ref midway); s.preRenderBitmap.MakeTransparent(); } finally { if (!src.IsNull) { FreeImage.UnloadEx(ref src); } if (!midway.IsNull) { FreeImage.UnloadEx(ref midway); } } return(RequestedAction.Cancel); }
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)); }