title: zoom out larger image to smaller in jrcrop image cropping plugin for angularjs
body: <p>I am trying capture images using camera plugin of cordova and crop image using jrCrop plugin in angularjs.  </p>
  
  <p>Both camera plugin of cordova and jrCrop plugin working well for cropping an image and store in file system.  </p>
  
  <p>problem in is there preview when smaller image is croping then container of jrCrop display image well but when the larger image is use for croping then it's not fit on the preview display.  </p>
  
  <p>original image (screenshot - 1)<br>
  <img src="https://i.stack.imgur.com/TwBxC.jpg" alt="enter image description here">  </p>
  
  <p>problem for large image (screenshot - 2)<br>
  <img src="https://i.stack.imgur.com/GgPmW.jpg" alt="enter image description here">  </p>
  
  <p>was i wanted to be (screenshot - 3)<br>
  <img src="https://i.stack.imgur.com/zn51k.jpg" alt="enter image description here">  </p>
  
  <p>Is there any method or solution for zoom out the larger image to fit in preview display of jrCrop.</p>
  
  <p><strong>Intialized jrCrop plugin :</strong>  </p>
  
  <pre><code>$jrCrop.crop({  
      url: imageSrc, //get image path from camera plugin
      width: $scope.imgWidth,
      height: $scope.imgHeight,
  }).then(function(canvas){
      UserImages.tmpImage=canvas.toDataURL();
      $location.path('/crop-image');
  });  
  </code></pre>
  
  <p><strong>Code : jrCrop.js</strong>  </p>
  
  <pre><code>angular.module('ionic')
  
  .factory('$jrCrop', [
      '$ionicModal',
      '$rootScope',
      '$q',
      function($ionicModal, $rootScope, $q) {
  
          var template = '&lt;div class="jr-crop modal" ng-style="{height: heightGap + \'px\'}" style="min-height:50px;" &gt;' +
                  '&lt;div class="jr-crop-title fmed-reg"&gt;Pinch to zoom in/out &lt;br&gt; Position photo with your finger&lt;/div&gt;' +
                  '&lt;div class="jr-crop-center-container"&gt;' +
                    '&lt;div class="jr-crop-img" ng-style="{width: width + \'px\', height: height + \'px\'}"&gt;&lt;/div&gt;' +
                  '&lt;/div&gt;' +
                  '&lt;div class="jr-crop-center-container"  style="opacity:0.5; background:#fff;border:1px solid #000;" &gt;' +
                    '&lt;div class="jr-crop-select" style="overflow: hidden;" ng-style="{width: width + \'px\', height: height + \'px\'}"&gt;&lt;/div&gt;' +
                  '&lt;/div&gt;' +
                  '&lt;div class="bar bar-footer bar-dark jr-crop-footer"&gt;' +
                    '&lt;button class="button button-clear fmed-reg jr-choose" ng-click="cancel()"&gt;Cancel&lt;/button&gt;' +
                    '&lt;div class="title"&gt;{{title}}&lt;/div&gt;' +
                    '&lt;button class="button button-clear fmed-reg jr-choose" ng-click="crop()"&gt;Choose&lt;/button&gt;' +
                  '&lt;/div&gt;' +
                '&lt;/div&gt;';
  
          var jrCropController = ionic.views.View.inherit({
              promise: null,
              imgWidth: null,
              imgHeight: null,
  
              // Elements that hold the cropped version and the full
              // overlaying image.
              imgSelect: null,
              imgFull: null,
  
              // Values exposed by scaling and moving. Needed
              // to calculate the result of cropped image
              posX: 0,
              posY: 0,
              scale: 1,
  
              initialize: function(options) {
                  var self = this;
  
                  this.options = options;
                  this.promise = $q.defer();
                  this.loadImage().then(function(elem) {
                  self.imgWidth = elem.naturalWidth;
                  self.imgHeight = elem.naturalHeight;
  
                  self.options.modal.el.querySelector('.jr-crop-img').appendChild(self.imgSelect = elem.cloneNode());
                  self.options.modal.el.querySelector('.jr-crop-select').appendChild(self.imgFull = elem.cloneNode());
  
                  self.bindHandlers();            
              });
  
          // options === scope. Expose actions for modal.
          this.options.cancel = this.cancel.bind(this);
          this.options.crop = this.crop.bind(this);
      },
  
      cancel: function() {
          var self = this;
  
          this.options.modal.remove().then(function() {
              self.promise.reject('canceled');
          });
      },
  
      /**
          * This is where the magic happens
      */
      bindHandlers: function() {
          var self = this,
  
          last_scale,
          last_posX = 0, last_posY = 0,
          min_pos_x = 0, min_pos_y = 0,
          max_pos_x = 0, max_pos_y = 0,
          transforming_correctX = 0, transforming_correctY = 0,
  
          scaleMin,
          image_ratio = self.imgWidth / self.imgHeight,
          cropper_ratio = self.options.width / self.options.height;
  
          if (cropper_ratio &lt; image_ratio) {
              scaleMin = self.options.height / self.imgHeight;
          } else {
              scaleMin = self.options.width / self.imgWidth;
          }
  
      function setPosWithinBoundaries() {
          calcMaxPos();
          if (self.posX &gt; min_pos_x) {
              self.posX = min_pos_x;
          }
          if (self.posX &lt; max_pos_x) {
              self.posX = max_pos_x;
          }
          if (self.posY &gt; min_pos_y) {
              self.posY = min_pos_y;
          }
          if (self.posY &lt; max_pos_y) {
              self.posY = max_pos_y;
          }
      }
  
      /**
          * Calculate the minimum and maximum positions.
          * This took some headaches to write, good luck
          * figuring this out.
      */
  
      function calcMaxPos() {
          // Calculate current proportions of the image.
          var currWidth = self.scale * self.imgWidth;
          var currHeight = self.scale * self.imgHeight;
  
          // Images are scaled from the center
          min_pos_x = (currWidth - self.imgWidth) / 2;
          min_pos_y = (currHeight - self.imgHeight) / 2;
          max_pos_x = -(currWidth - min_pos_x - self.options.width);
          max_pos_y = -(currHeight - min_pos_y - self.options.height);
      }
  
      // Based on: http://stackoverflow.com/questions/18011099/pinch-to-zoom-using-hammer-js
  
      ionic.onGesture('touch transform drag dragstart dragend', function(e) {
          switch (e.type) {
              case 'touch':
                  last_scale = self.scale;
                  break;
              case 'drag':
                  self.posX = last_posX + e.gesture.deltaX - transforming_correctX;
                  self.posY = last_posY + e.gesture.deltaY - transforming_correctY;
                  setPosWithinBoundaries();
                  break;
              case 'transform':
                  self.scale = Math.max(scaleMin, Math.min(last_scale * e.gesture.scale, 10));
                  setPosWithinBoundaries();
                  break;
              case 'dragend':
                  last_posX = self.posX;
                  last_posY = self.posY;
          $(".jr-crop-select").show();
                  break;
              case 'dragstart':
                  last_scale = self.scale;
  
                  // After scaling, hammerjs needs time to reset the deltaX and deltaY values,
                  // when the user drags the image before this is done the image will jump.
                  // This is an attempt to fix that.
                  if (e.gesture.deltaX &gt; 1 || e.gesture.deltaX &lt; -1) {
                      transforming_correctX = e.gesture.deltaX;
                      transforming_correctY = e.gesture.deltaY;
                  } else {
                      transforming_correctX = 0;
                      transforming_correctY = 0;
                  }
                  break;
          }
  
          var transform ='translate3d(' + self.posX + 'px,' + self.posY + 'px, 0) ' +'scale3d(' + self.scale + ',' + self.scale + ', 0)';
  
              self.imgSelect.style[ionic.CSS.TRANSFORM] = transform;
              self.imgFull.style[ionic.CSS.TRANSFORM] = transform;
  
          }, self.options.modal.el);
  
      },
  
      /**
          * Calculate the new image from the values calculated by
          * user input. Return a canvas-object with the image on it.
          * 
          * Note: It doesn't actually downsize the image, it only returns
          * a cropped version. Since there's inconsistenties in image-quality
          * when downsizing it's up to the developer to implement this. Preferably
          * on the server.
      */
      crop: function() {
          var canvas = document.createElement('canvas');
          var context = canvas.getContext('2d');
  
          // Canvas size is original proportions but scaled down.
          canvas.width = this.options.width / this.scale;
          canvas.height = this.options.height / this.scale;
  
          // The full proportions 
          var currWidth = this.imgWidth * this.scale;
          var currHeight = this.imgHeight * this.scale;
  
          // Because the top/left position doesn't take the scale of the image in
          // we need to correct this value.
          var correctX = (currWidth - this.imgWidth) / 2;
          var correctY = (currHeight - this.imgHeight) / 2;
  
          var sourceX = (this.posX - correctX) / this.scale;
          var sourceY = (this.posY - correctY) / this.scale;
  
          context.drawImage(this.imgFull, sourceX, sourceY);
  
          this.options.modal.remove();
          this.promise.resolve(canvas);
      },
  
      /**
          * Load the image and return the element.
          * Return Promise that will fail when unable to load image.
      */
      loadImage: function() {
          var promise = $q.defer();
  
          // Load the image and resolve with the DOM node when done.
          angular.element('&lt;img /&gt;')
          .bind('load', function(e) {
              promise.resolve(this);
          })
          .bind('error', promise.reject)
          .prop('src', this.options.url);
  
      //this.options.heightGap=200;
          // Return the promise
          return promise.promise;
        }
      });
  
      return {
          crop: function(options) {
              var scope = $rootScope.$new(true);
  
              ionic.Utils.extend(scope, options);
  
              scope.modal = $ionicModal.fromTemplate(template, {
              scope: scope
          });
  
          // Show modal and initialize cropper.
          return scope.modal.show().then(function() {
          return (new jrCropController(scope)).promise.promise;
      });
     }
    };
  }]);
  </code></pre>