Rect Object

The Rect class is the heart of the Jcrop engine. While it may appear to be a plain old Javascript object, it has some unique behavior that makes it easy to work with coordinates and crop selections.

A Rect object instance represents coordinates for an arbitrary rectangle, usually top left and bottom right. Internally the class is used to set or describe an existing crop selection. You may use Rect objects when using the Jcrop API or to describe rectangles within your own application.

Properties

The native properties of a Rect object are x, y, w, h. These properties should only contain integers or floating point numbers that represent the x and y top left corner of the crop, and the crop dimentions as w and h for width and height, respectively.

Getters and setters exist for x2 and y2, if it is desirable to know the secondary coordinates (usually the bottom-right point). If you set x2 or y2, the object's w and h values are updated as if the secondary point had been moved.

Additionally, x1 and y1 setters exist for cases where you want to move the primary point without without moving the entire object. Another way to say this is that w and h will be adjusted as if only this point moved.

To get the aspect ratio of the Rect object's current values, a getter named aspect exists.

Instance Methods

rect.normalize()

Rect coordinates may have negative values for w or h if the secondary point is above or to the left of the primary point. Calling the normalize() method will return a new Rect object with adjusted values indicating a positive w and h. This method is used internally by Jcrop to provide values that are easily rendered regardless of the current orientation of the coordinates, such as during a dragging operation.

const r = Jcrop.Rect.create(110,110,-100,-50);
const s = r.normalize();
console.log(r.x,r.w); // --> 110, -100
console.log(s.x,s.w); // --> 10, 100

rect.rebound(w,h)

The rebound() method will move the Rect coordinates back into an area inside of a box defined by the w,h parameters given. It returns a Rect object with the rebounded coordinates. This method is used internally to prevent a bounded crop from being dragged outside the boundaries of the stage element.

const r = Jcrop.Rect.create(-15,-20,100,50);
const s = r.rebound(200,200);
console.log(r.x,r.y); // --> -15, -20
console.log(s.x,s.y); // --> 0, 0

rect.scale(x,y)

Scales the width and height based on x,y scaling values, usually somewhere between 1 and 0 for a percentage. Returns a fresh Rect object with the width and height scaled.

rect.center(w,h)

Returns a new Rect object with x,y producing a region centered within the space defined by w,h.

rect.round()

Especially when using aspect ratios, fractional values are very possible, but not always desirable. Call the round() method and get a totally new Rect with rounded values.

Static Methods

Rect.create(x,y,w,h)

Creates a new Rect object with the given values.

const r = Jcrop.Rect.create(10,10,100,100);

Rect.from(el)

Creates a new Rect object by reading offsetTop, offsetLeft, offsetWidth and offsetHeight from the given DOM object. The object should probably be in the DOM, as not all of these properties may be set on an element that is not in the DOM.

const el = document.getElementById('crop1');
const r = Jcrop.Rect.from(el);

Rect.fromPoints(p1,p2)

Creates a new Rect from coordinate points specified as arrays (e.g. [x1,y1] and [x2,y2]).

const p1 = [10,10], p2 = [110,110];
const r = Jcrop.Rect.fromPoints(p1,p2);

Rect.fromPoint(p1,w,h,quad)

Creates a new Rect object anchored at a single point, such as [10,10]. The Rect will be of size w and h and the direction is determined by the value of quad which must be one of tl, tr, bl, or br (the default) indicating top/bottom left/right quadrant.

const point = [10,10];
const r = Jcrop.Rect.fromPoint(point,100,100,'br');

Rect.getMax(w,h,aspect)

Returns an array of [w,h] representing maximum rectangle size of ratio that fits within the given w,h size passed as parameters. This function is used in conjunction with Rect.fromPoint() to perform aspect ratio filtering when that feature is enabled.

const ratio = 4/3;
const [w,h] = Jcrop.Rect.getMax(200,150,ratio);

Source Code

/* Rect class -- describes a rectangle with two points, usually
   top left and bottom right. It allows the second set of coordinates
   to be described as either w/h or x2/y2 and allows getting and
   setting of those values such that the object values will always be
   consistent with the latest input. It should be noted that it does not
   attempt to keep these points normalized. That is, you should expect
   to see the actual w/h properties to sometimes be negative values.
   To normalize the values, use the normalize method, which will return
   a new Rect object with normalized values.
*/
class Rect {

  constructor () {
    this.x = 0;
    this.y = 0;
    this.w = 0;
    this.h = 0;
  }

  set x1 (v) {
    this.w = this.x2 - v;
    this.x = v;
  }

  set y1 (v) {
    this.h = this.y2 - v;
    this.y = v;
  }

  get x2 () {
    return this.x + this.w;
  }

  set x2 (x) {
    this.w = x - this.x;
  }

  get y2 () {
    return this.y + this.h;
  }

  get aspect () {
    return this.w/this.h;
  }

  set y2 (y) {
    this.h = y - this.y;
  }

  round () {
    return Rect.create(
      Math.round(this.x),
      Math.round(this.y),
      Math.round(this.w),
      Math.round(this.h)
    );
  }

  normalize () {
    const [x1,y1,x2,y2] = [
      Math.min(this.x,this.x2),
      Math.min(this.y,this.y2),
      Math.max(this.x,this.x2),
      Math.max(this.y,this.y2)
    ];
    return Rect.create(x1,y1,x2-x1,y2-y1);
  }

  rebound (w,h) {
    const rect = this.normalize();
    if (rect.x<0) rect.x = 0;
    if (rect.y<0) rect.y = 0;
    if (rect.x2>w) rect.x = w-rect.w;
    if (rect.y2>h) rect.y = h-rect.h;
    return rect;
  }

  scale (x,y) {
    y = y || x;
    return Rect.create(this.x*x,this.y*y,this.w*x,this.h*y);
  }

  unscale (x,y) {
    y = y || x;
    return Rect.create(this.x/x,this.y/y,this.w/x,this.h/y);
  }

  center (w,h) {
    return Rect.create(
      (w - this.w)/2,
      (h - this.h)/2,
      this.w, this.h
    );
  }

  toArray () {
    return [ this.x, this.y, this.w, this.h ];
  }
}

Rect.fromPoints = function (p1,p2) {
  const [x1,y1,x2,y2] = [
    Math.min(p1[0],p2[0]),
    Math.min(p1[1],p2[1]),
    Math.max(p1[0],p2[0]),
    Math.max(p1[1],p2[1])
  ];
  return Rect.create(x1,y1,x2-x1,y2-y1);
};

Rect.create = function (x=0,y=0,w=0,h=0) {
  const c = new Rect();
  c.x = x;
  c.y = y;
  c.w = w;
  c.h = h;
  return c;
};

Rect.from = function (el) {
  if (Array.isArray(el)) return Rect.fromArray(el);
  const c = new Rect();
  c.x = el.offsetLeft;
  c.y = el.offsetTop;
  c.w = el.offsetWidth;
  c.h = el.offsetHeight;
  return c;
};

Rect.fromArray = function (args) {
  if (args.length === 4) return Rect.create.apply(this,args);
  else if (args.length === 2) return Rect.fromPoints(args[0],args[1]);
  else throw new Error('fromArray method problem');
};

Rect.sizeOf = function (el,y) {
  if (y) return Rect.create(0,0,el,y);
  const c = new Rect();
  c.w = el.offsetWidth;
  c.h = el.offsetHeight;
  return c;
};

Rect.getMax = function (w,h,aspect) {
  if ((w/h) > aspect) return [ h*aspect, h ];
  else return [ w, w/aspect ];
};

Rect.fromPoint = function (point,w,h,quad='br') {
  const c = new Rect();
  c.x = point[0];
  c.y = point[1];
  switch (quad) {
    case 'br':
      c.x2 = c.x + w;
      c.y2 = c.y + h;
      break;
    case 'bl':
      c.x2 = c.x - w;
      c.y2 = c.y + h;
      break;
    case 'tl':
      c.x2 = c.x - w;
      c.y2 = c.y - h;
      break;
    case 'tr':
      c.x2 = c.x + w;
      c.y2 = c.y - h;
      break;
  }
  return c;
};

export default Rect;