You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

imagediff.js 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import $ from 'jquery';
  2. import {hideElem} from '../utils/dom.js';
  3. function getDefaultSvgBoundsIfUndefined(svgXml, src) {
  4. const DefaultSize = 300;
  5. const MaxSize = 99999;
  6. const svg = svgXml.documentElement;
  7. const width = svg?.width?.baseVal;
  8. const height = svg?.height?.baseVal;
  9. if (width === undefined || height === undefined) {
  10. return null; // in case some svg is invalid or doesn't have the width/height
  11. }
  12. if (width.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE || height.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE) {
  13. const img = new Image();
  14. img.src = src;
  15. if (img.width > 1 && img.width < MaxSize && img.height > 1 && img.height < MaxSize) {
  16. return {
  17. width: img.width,
  18. height: img.height
  19. };
  20. }
  21. if (svg.hasAttribute('viewBox')) {
  22. const viewBox = svg.viewBox.baseVal;
  23. return {
  24. width: DefaultSize,
  25. height: DefaultSize * viewBox.width / viewBox.height
  26. };
  27. }
  28. return {
  29. width: DefaultSize,
  30. height: DefaultSize
  31. };
  32. }
  33. return null;
  34. }
  35. export function initImageDiff() {
  36. function createContext(image1, image2) {
  37. const size1 = {
  38. width: image1 && image1.width || 0,
  39. height: image1 && image1.height || 0
  40. };
  41. const size2 = {
  42. width: image2 && image2.width || 0,
  43. height: image2 && image2.height || 0
  44. };
  45. const max = {
  46. width: Math.max(size2.width, size1.width),
  47. height: Math.max(size2.height, size1.height)
  48. };
  49. return {
  50. image1: $(image1),
  51. image2: $(image2),
  52. size1,
  53. size2,
  54. max,
  55. ratio: [
  56. Math.floor(max.width - size1.width) / 2,
  57. Math.floor(max.height - size1.height) / 2,
  58. Math.floor(max.width - size2.width) / 2,
  59. Math.floor(max.height - size2.height) / 2
  60. ]
  61. };
  62. }
  63. $('.image-diff').each(function() {
  64. const $container = $(this);
  65. // the container may be hidden by "viewed" checkbox, so use the parent's width for reference
  66. const diffContainerWidth = Math.max($container.closest('.diff-file-box').width() - 300, 100);
  67. const pathAfter = $container.data('path-after');
  68. const pathBefore = $container.data('path-before');
  69. const imageInfos = [{
  70. loaded: false,
  71. path: pathAfter,
  72. $image: $container.find('img.image-after'),
  73. $boundsInfo: $container.find('.bounds-info-after')
  74. }, {
  75. loaded: false,
  76. path: pathBefore,
  77. $image: $container.find('img.image-before'),
  78. $boundsInfo: $container.find('.bounds-info-before')
  79. }];
  80. for (const info of imageInfos) {
  81. if (info.$image.length > 0) {
  82. $.ajax({
  83. url: info.path,
  84. success: (data, _, jqXHR) => {
  85. info.$image.on('load', () => {
  86. info.loaded = true;
  87. setReadyIfLoaded();
  88. }).on('error', () => {
  89. info.loaded = true;
  90. setReadyIfLoaded();
  91. info.$boundsInfo.text('(image error)');
  92. });
  93. info.$image.attr('src', info.path);
  94. if (jqXHR.getResponseHeader('Content-Type') === 'image/svg+xml') {
  95. const bounds = getDefaultSvgBoundsIfUndefined(data, info.path);
  96. if (bounds) {
  97. info.$image.attr('width', bounds.width);
  98. info.$image.attr('height', bounds.height);
  99. hideElem(info.$boundsInfo);
  100. }
  101. }
  102. }
  103. });
  104. } else {
  105. info.loaded = true;
  106. setReadyIfLoaded();
  107. }
  108. }
  109. function setReadyIfLoaded() {
  110. if (imageInfos[0].loaded && imageInfos[1].loaded) {
  111. initViews(imageInfos[0].$image, imageInfos[1].$image);
  112. }
  113. }
  114. function initViews($imageAfter, $imageBefore) {
  115. initSideBySide(createContext($imageAfter[0], $imageBefore[0]));
  116. if ($imageAfter.length > 0 && $imageBefore.length > 0) {
  117. initSwipe(createContext($imageAfter[1], $imageBefore[1]));
  118. initOverlay(createContext($imageAfter[2], $imageBefore[2]));
  119. }
  120. hideElem($container.find('> .loader'));
  121. $container.find('> .gt-hidden').removeClass('gt-hidden');
  122. }
  123. function initSideBySide(sizes) {
  124. let factor = 1;
  125. if (sizes.max.width > (diffContainerWidth - 24) / 2) {
  126. factor = (diffContainerWidth - 24) / 2 / sizes.max.width;
  127. }
  128. const widthChanged = sizes.image1.length !== 0 && sizes.image2.length !== 0 && sizes.image1[0].naturalWidth !== sizes.image2[0].naturalWidth;
  129. const heightChanged = sizes.image1.length !== 0 && sizes.image2.length !== 0 && sizes.image1[0].naturalHeight !== sizes.image2[0].naturalHeight;
  130. if (sizes.image1.length !== 0) {
  131. $container.find('.bounds-info-after .bounds-info-width').text(`${sizes.image1[0].naturalWidth}px`).addClass(widthChanged ? 'green' : '');
  132. $container.find('.bounds-info-after .bounds-info-height').text(`${sizes.image1[0].naturalHeight}px`).addClass(heightChanged ? 'green' : '');
  133. }
  134. if (sizes.image2.length !== 0) {
  135. $container.find('.bounds-info-before .bounds-info-width').text(`${sizes.image2[0].naturalWidth}px`).addClass(widthChanged ? 'red' : '');
  136. $container.find('.bounds-info-before .bounds-info-height').text(`${sizes.image2[0].naturalHeight}px`).addClass(heightChanged ? 'red' : '');
  137. }
  138. sizes.image1.css({
  139. width: sizes.size1.width * factor,
  140. height: sizes.size1.height * factor
  141. });
  142. sizes.image1.parent().css({
  143. margin: `${sizes.ratio[1] * factor + 15}px ${sizes.ratio[0] * factor}px ${sizes.ratio[1] * factor}px`,
  144. width: sizes.size1.width * factor + 2,
  145. height: sizes.size1.height * factor + 2
  146. });
  147. sizes.image2.css({
  148. width: sizes.size2.width * factor,
  149. height: sizes.size2.height * factor
  150. });
  151. sizes.image2.parent().css({
  152. margin: `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`,
  153. width: sizes.size2.width * factor + 2,
  154. height: sizes.size2.height * factor + 2
  155. });
  156. }
  157. function initSwipe(sizes) {
  158. let factor = 1;
  159. if (sizes.max.width > diffContainerWidth - 12) {
  160. factor = (diffContainerWidth - 12) / sizes.max.width;
  161. }
  162. sizes.image1.css({
  163. width: sizes.size1.width * factor,
  164. height: sizes.size1.height * factor
  165. });
  166. sizes.image1.parent().css({
  167. margin: `0px ${sizes.ratio[0] * factor}px`,
  168. width: sizes.size1.width * factor + 2,
  169. height: sizes.size1.height * factor + 2
  170. });
  171. sizes.image1.parent().parent().css({
  172. padding: `${sizes.ratio[1] * factor}px 0 0 0`,
  173. width: sizes.max.width * factor + 2
  174. });
  175. sizes.image2.css({
  176. width: sizes.size2.width * factor,
  177. height: sizes.size2.height * factor
  178. });
  179. sizes.image2.parent().css({
  180. margin: `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`,
  181. width: sizes.size2.width * factor + 2,
  182. height: sizes.size2.height * factor + 2
  183. });
  184. sizes.image2.parent().parent().css({
  185. width: sizes.max.width * factor + 2,
  186. height: sizes.max.height * factor + 2
  187. });
  188. $container.find('.diff-swipe').css({
  189. width: sizes.max.width * factor + 2,
  190. height: sizes.max.height * factor + 4
  191. });
  192. $container.find('.swipe-bar').on('mousedown', function(e) {
  193. e.preventDefault();
  194. const $swipeBar = $(this);
  195. const $swipeFrame = $swipeBar.parent();
  196. const width = $swipeFrame.width() - $swipeBar.width() - 2;
  197. $(document).on('mousemove.diff-swipe', (e2) => {
  198. e2.preventDefault();
  199. const value = Math.max(0, Math.min(e2.clientX - $swipeFrame.offset().left, width));
  200. $swipeBar.css({
  201. left: value
  202. });
  203. $container.find('.swipe-container').css({
  204. width: $swipeFrame.width() - value
  205. });
  206. $(document).on('mouseup.diff-swipe', () => {
  207. $(document).off('.diff-swipe');
  208. });
  209. });
  210. });
  211. }
  212. function initOverlay(sizes) {
  213. let factor = 1;
  214. if (sizes.max.width > diffContainerWidth - 12) {
  215. factor = (diffContainerWidth - 12) / sizes.max.width;
  216. }
  217. sizes.image1.css({
  218. width: sizes.size1.width * factor,
  219. height: sizes.size1.height * factor
  220. });
  221. sizes.image2.css({
  222. width: sizes.size2.width * factor,
  223. height: sizes.size2.height * factor
  224. });
  225. sizes.image1.parent().css({
  226. margin: `${sizes.ratio[1] * factor}px ${sizes.ratio[0] * factor}px`,
  227. width: sizes.size1.width * factor + 2,
  228. height: sizes.size1.height * factor + 2
  229. });
  230. sizes.image2.parent().css({
  231. margin: `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`,
  232. width: sizes.size2.width * factor + 2,
  233. height: sizes.size2.height * factor + 2
  234. });
  235. sizes.image2.parent().parent().css({
  236. width: sizes.max.width * factor + 2,
  237. height: sizes.max.height * factor + 2
  238. });
  239. $container.find('.onion-skin').css({
  240. width: sizes.max.width * factor + 2,
  241. height: sizes.max.height * factor + 4
  242. });
  243. const $range = $container.find("input[type='range']");
  244. const onInput = () => sizes.image1.parent().css({
  245. opacity: $range.val() / 100
  246. });
  247. $range.on('input', onInput);
  248. onInput();
  249. }
  250. });
  251. }