PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/Eksponent.CropUp.UmbracoIV/Resources/CropUp.js

http://cropup.codeplex.com
JavaScript | 441 lines | 316 code | 88 blank | 37 comment | 90 complexity | fe895dca4d70b20d3cdfe30863269b6f MD5 | raw file
  1. //////////////////////////////////////////////////////////////////////////
  2. // //
  3. // Eksponent CropUp (www.eksponent.com) //
  4. // Copyright(c) @nielskuhnel (Niels Kühnel, Eksponent). License: MIT. //
  5. // //
  6. //////////////////////////////////////////////////////////////////////////
  7. // This code was ported from C#. Hence, there may be some weird constructs below.
  8. var CropUp = (function () {
  9. var _referenceWidth = 620;
  10. var _referenceHeight = 480;
  11. var _defaultAnimate = { duration: 0 };
  12. function update() {
  13. $(".crop-up").each(function () {
  14. var currentSize = $(this).data("current-size");
  15. var size = [$(this).outerWidth(), $(this).outerHeight()];
  16. if (!currentSize || size[0] !== currentSize[0] || size[1] != currentSize[1]) {
  17. $(this).cropUp();
  18. }
  19. });
  20. }
  21. (function () {
  22. $(function () {
  23. update();
  24. var to;
  25. $(window).resize(function () {
  26. clearTimeout(to);
  27. to = setTimeout(function () {
  28. update();
  29. }, 1);
  30. });
  31. });
  32. })(jQuery);
  33. jQuery.fn.cropUp = function (options, animateOptions) {
  34. $(this).each(function () {
  35. CropUp.update($(this), options, animateOptions);
  36. });
  37. };
  38. function expand(lbub, targetWidth, min, max, bias) {
  39. var width = lbub[1] - lbub[0];
  40. lbub[0] -= Math.floor((targetWidth - width) * bias);
  41. lbub[1] += Math.floor((targetWidth - width) * (1 - bias));
  42. if (lbub[1] - lbub[0] > width) --lbub[0];
  43. if (lbub[0] < min) {
  44. lbub[1] += min - lbub[0];
  45. lbub[0] += min - lbub[0];
  46. if (lbub[1] > max) {
  47. //Exceeds max
  48. return lbub[1] - max;
  49. }
  50. }
  51. if (lbub[1] > max) {
  52. lbub[0] -= lbub[1] - max;
  53. lbub[1] -= lbub[1] - max;
  54. if (lbub[0] < min) {
  55. //Exceeds min
  56. return min - lbub[0];
  57. }
  58. }
  59. return 0;
  60. }
  61. return {
  62. setReference: function (w, h) {
  63. _referenceWidth = w;
  64. _referenceHeight = h;
  65. },
  66. setAnimate: function (animate) {
  67. _defaultAnimate = animate;
  68. },
  69. measureImage: function (img, src, complete) {
  70. var background = false;
  71. var imageElement = img;
  72. if ($(img).attr("src")) {
  73. $(img).data("img-no-gc", img = new Image());
  74. background = true;
  75. } else {
  76. $(img).attr("src", src);
  77. }
  78. img.onload = function () {
  79. img.onload = null; complete(this.width, this.height); if (background) imageElement.src = src;
  80. };
  81. img.src = src;
  82. },
  83. defaultCropping: function (width, height) {
  84. return {
  85. topLeft: [0, 0],
  86. bottomRight: [width, height],
  87. calculated: true,
  88. bias: [width / 2, height / 2]
  89. };
  90. },
  91. suggest: function (originalWidth, originalHeight, cropWidth, cropHeight, basis) {
  92. var c = CropUp.getCropping(originalWidth, originalHeight, cropWidth, cropHeight, basis);
  93. return {
  94. calculated: true,
  95. topLeft: c.topLeft,
  96. bottomRight: c.bottomRight
  97. };
  98. },
  99. getCropping: function (originalWidth, originalHeight, targetWidth, targetHeight, info, resizeToCropping, zoom) {
  100. if (info === null) info = CropInfo.Default(originalWidth, originalHeight);
  101. var orgWidth = originalWidth, orgHeight = originalHeight;
  102. var orgAspect = orgWidth / orgHeight;
  103. var maxWidth = targetWidth === null ? -1 : Math.min(zoom ? targetWidth : originalWidth, targetWidth),
  104. maxHeight = targetHeight === null ? -1 : Math.min(zoom ? targetHeight : originalHeight, targetHeight);
  105. var cropWidth = (info.bottomRight[0] - info.topLeft[0]);
  106. var cropHeight = (info.bottomRight[1] - info.topLeft[1]);
  107. var cropAspect = cropWidth / cropHeight;
  108. if (targetWidth === null && targetHeight === null) {
  109. maxWidth = resizeToCropping ? cropWidth : orgWidth;
  110. maxHeight = resizeToCropping ? cropHeight : orgHeight;
  111. }
  112. else if (targetWidth === null) {
  113. maxWidth = Math.floor(maxHeight * (resizeToCropping ? cropAspect : orgAspect));
  114. }
  115. else if (targetHeight === null) {
  116. maxHeight = Math.floor(maxWidth / (resizeToCropping ? cropAspect : orgAspect));
  117. }
  118. var targetAspect = maxWidth / maxHeight;
  119. var zoom;
  120. if (info.calculated) {
  121. //Calculate zoom reduction
  122. var zoomedWidth = Math.max(maxWidth, _referenceWidth);
  123. var zoomedHeight = Math.max(maxHeight, _referenceHeight);
  124. zoom = Math.max(zoomedHeight / orgHeight, zoomedWidth / orgWidth);
  125. orgWidth *= zoom;
  126. orgHeight *= zoom;
  127. cropWidth *= zoom;
  128. cropHeight *= zoom;
  129. }
  130. else {
  131. zoom = 1;
  132. }
  133. var xs = [info.topLeft[0] * zoom, info.bottomRight[0] * zoom];
  134. var ys = [info.topLeft[1] * zoom, info.bottomRight[1] * zoom];
  135. var bias = info.bias !== null ?
  136. [info.bias[0] * zoom, info.bias[1] * zoom]
  137. : [xs[0] + ((xs[1] - xs[0]) / 2), ys[0] + ((ys[1] - ys[0]) / 2)];
  138. var xbias = info.bias !== null ? 1 - (xs[1] - bias[0]) / (xs[1] - xs[0]) : .5;
  139. var ybias = info.bias !== null ? 1 - (ys[1] - bias[1]) / (ys[1] - ys[0]) : .5;
  140. if (maxWidth > cropWidth && maxHeight > cropHeight) {
  141. //Simply expand around crop area
  142. if (expand(xs, maxWidth, 0, orgWidth, xbias) != 0) {
  143. xs[0] = 0;
  144. xs[1] = orgWidth;
  145. }
  146. if (expand(ys, maxHeight, 0, orgHeight, ybias) != 0) {
  147. ys[0] = 0;
  148. ys[1] = orgHeight;
  149. }
  150. }
  151. else if (Math.abs(targetAspect - cropAspect) > 0.001) {
  152. var biasedWidth = 0, biasedHeight = 0;
  153. if (cropAspect < targetAspect) //Grow width
  154. {
  155. //Add image from outside the cropping box to fit target aspect ratio.
  156. var newWidth = cropHeight * targetAspect;
  157. var exceeded = expand(xs, newWidth, 0, orgWidth, xbias);
  158. if (exceeded > 0) {
  159. biasedWidth = orgWidth;
  160. if (info.bias !== null) {
  161. //Keep as much context as possible
  162. biasedHeight = biasedWidth / targetAspect;
  163. }
  164. else {
  165. //Pad rather than crop. Cropped area must be shown entirely
  166. biasedHeight = cropHeight;
  167. }
  168. }
  169. }
  170. else //Grow height
  171. {
  172. //Add image from outside the cropping box to fit target aspect ratio.
  173. var newHeight = cropWidth / targetAspect;
  174. var exceeded = expand(ys, newHeight, 0, orgHeight, ybias);
  175. if (exceeded > 0) //We need to crop the the image inside the cropping box
  176. {
  177. biasedHeight = orgHeight;
  178. if (info.bias !== null) {
  179. //Keep as much context as possible
  180. biasedWidth = biasedHeight * targetAspect;
  181. }
  182. else {
  183. //Pad rather than crop. Cropped area must be shown entirely
  184. biasedWidth = cropWidth;
  185. }
  186. }
  187. }
  188. if (biasedHeight != 0 || biasedWidth != 0) {
  189. //Center around bias point. If needed add less to lower side and more to higher. (1 - 0.5 = 1, 1 + 0.5 = 2)
  190. xs[0] = Math.max(0, Math.max(bias[0] - Math.floor(.5 * biasedWidth), info.topLeft[0] * zoom));
  191. ys[0] = Math.max(0, Math.max(bias[1] - Math.floor(.5 * biasedHeight), info.topLeft[1] * zoom));
  192. //Keep in box
  193. if (xs[0] + biasedWidth > info.bottomRight[0] * zoom) xs[0] = Math.max(0, info.bottomRight[0] * zoom - biasedWidth);
  194. if (ys[0] + biasedHeight > info.bottomRight[1] * zoom) ys[0] = Math.max(0, info.bottomRight[1] * zoom - biasedHeight);
  195. //Keep in image
  196. if (xs[0] + biasedWidth > orgWidth) xs[0] = Math.max(0, orgWidth - biasedWidth);
  197. if (ys[0] + biasedHeight > orgHeight) ys[0] = Math.max(0, orgHeight - biasedHeight);
  198. //Set end of span
  199. xs[1] = xs[0] + biasedWidth;
  200. ys[1] = ys[0] + biasedHeight;
  201. }
  202. }
  203. return {
  204. topLeft: [xs[0] / zoom, ys[0] / zoom],
  205. bottomRight: [xs[1] / zoom, ys[1] / zoom],
  206. xbias: xbias,
  207. ybias: ybias,
  208. targetWidth: Math.floor(maxWidth),
  209. targetHeight: Math.floor(maxHeight)
  210. };
  211. },
  212. update: function (container, options, animateOptions) {
  213. container = $(container);
  214. options = options || {};
  215. animateOptions = animateOptions || _defaultAnimate;
  216. if (container.is("img")) {
  217. img = container;
  218. container = img.parent();
  219. if (!container.is(".crop-up")) {
  220. img.removeClass("crop-up").wrap("<div class='crop-up'></div>");
  221. container = img.parent();
  222. container.data(img.data()).attr("style", img.attr("style"));
  223. img.attr("style", "");
  224. }
  225. }
  226. var img = $("img", container);
  227. if (!img.length) {
  228. container.append(img = $("<img />"));
  229. } else if (!container.data("image")) {
  230. var src = img.attr("src");
  231. if (src) container.data("image", src);
  232. }
  233. img.css("position", "absolute");
  234. if (container.css("position") == "static") {
  235. container.css("position", "relative");
  236. }
  237. container.css("overflow", "hidden");
  238. for (var s in options) {
  239. var v = options[s];
  240. container.data(s, !v ? null : v.join ? v.join() : v);
  241. }
  242. var size = img.data("size");
  243. var targetSize = [1 * container.innerWidth(), 1 * container.innerHeight()];
  244. var cropBoxData = container.data("box");
  245. var gravityData = container.data("gravity");
  246. //Progressive loading
  247. if (container.data("upscale-url") && !img.data("maxed-out")) {
  248. var step = container.data("upscale-step") || 0;
  249. if (!container.data("loading")) {
  250. var firstLoad = !size;
  251. size = size || [0, 0];
  252. var requiredSize = [targetSize[0], targetSize[1]];
  253. var changed = false;
  254. for (var i = 0; i <= 1; i++) {
  255. if (step == 0) {
  256. changed = changed || requiredSize[i] != size[i];
  257. } else {
  258. if (requiredSize[i] > size[i]) {
  259. changed = true;
  260. }
  261. requiredSize[i] = (1 + Math.floor(requiredSize[i] / step)) * step;
  262. }
  263. }
  264. if (changed || !size) {
  265. container.data("loading", true);
  266. if (firstLoad) {
  267. requiredSize = [step, step];
  268. }
  269. var src = container.data("upscale-url").replace("{w}", requiredSize[0]).replace("{h}", requiredSize[1]);
  270. CropUp.measureImage(img[0], src, function (w, h) {
  271. //Dear reader. This code is not pretty. Skip this... :)
  272. if (w < requiredSize[0] || h < requiredSize[1]) {
  273. //The image returned from the server was smaller than the requested.
  274. //Switch to client side scaling to avoid zooming on the server
  275. img.css({ position: "absolute" });
  276. /*src = container.data("upscale-url").replace("{w}", 10000).replace("{h}", 10000);
  277. CropUp.measureImage(img[0], src, function (ww, hh) {
  278. img.data("size", [ww, hh]);
  279. img.data("maxed-out", true);
  280. container.data("loading", false);
  281. CropUp.update(container);
  282. });*/
  283. img.data("size", [w, h]);
  284. img.data("maxed-out", true);
  285. container.data("loading", false);
  286. CropUp.update(container);
  287. return;
  288. }
  289. img.data("size", [w, h]);
  290. if (step == 0) {
  291. img.css({ position: "static", width: w + "px", height: h + "px" });
  292. }
  293. container.data("loading", false);
  294. CropUp.update(container);
  295. });
  296. }
  297. }
  298. //No image is loaded wait for it
  299. if (!size || size[0] == 0 || size[1] == 0) return;
  300. //Static images from the server are used. Stop processing.
  301. if (step == 0) return;
  302. } else if (!size) {
  303. //Static image
  304. var src = container.data("image");
  305. CropUp.measureImage(img, src, function (w, h) {
  306. img.data("size", [w, h]);
  307. CropUp.update(container);
  308. });
  309. return;
  310. }
  311. container.addClass("loaded");
  312. var cropBox = [0, 0, size[0], size[1]];
  313. if (cropBoxData) {
  314. cropBox = typeof cropBoxData == "string" ? cropBoxData.split(",") : cropBoxData;
  315. cropBox = [Math.round(size[0] * 1 * cropBox[0]),
  316. Math.round(size[1] * 1 * cropBox[1]),
  317. Math.round(size[0] * 1 * cropBox[2]),
  318. Math.round(size[1] * 1 * cropBox[3])];
  319. }
  320. var gravity = [Math.round(1 * cropBox[0] + (1 * cropBox[2] - 1 * cropBox[0]) / 2),
  321. Math.round(1 * cropBox[1] + (1 * cropBox[3] - 1 * cropBox[1]) / 2)];
  322. if (gravityData) {
  323. gravity = typeof gravityData == "string" ? gravityData.split(",") : gravityData;
  324. gravity = [Math.round(1 * gravity[0] * size[0]), Math.round(1 * gravity[1] * size[1])];
  325. }
  326. var info = {
  327. topLeft: [1 * cropBox[0], 1 * cropBox[1]],
  328. bottomRight: [1 * cropBox[2], 1 * cropBox[3]],
  329. bias: [1 * gravity[0], 1 * gravity[1]],
  330. calculated: true
  331. };
  332. var zoom = container.data("zoom");
  333. var fixed = container.data("fixed");
  334. var c = CropUp.getCropping(size[0], size[1],
  335. targetSize[0],
  336. targetSize[1], info, fixed, zoom);
  337. //Adjust image size
  338. var xZoom = Math.min(zoom ? c.targetHeight : size[1], c.targetHeight) / (c.bottomRight[1] - c.topLeft[1]);
  339. var yZoom = Math.min(zoom ? c.targetWidth : size[0], c.targetWidth) / (c.bottomRight[0] - c.topLeft[0]);
  340. c.zoom = Math.min(xZoom, yZoom);
  341. var opts = {
  342. width: Math.round(size[0] * c.zoom),
  343. height: Math.round(size[1] * c.zoom),
  344. left: Math.round(-c.topLeft[0] * c.zoom),
  345. top: Math.round(-c.topLeft[1] * c.zoom)
  346. };
  347. //Pad if not upscaling
  348. if (opts.width < targetSize[0]) opts.left += (targetSize[0] - opts.width) / 2;
  349. if (opts.height < targetSize[1]) opts.top += (targetSize[1] - opts.height) / 2;
  350. img.stop().animate(opts, animateOptions);
  351. }
  352. };
  353. })();