Source: media/thumbnail/thumbnailGalleryCover.js

  1. import { WebVTTThumbnail } from "./WebVTTThumbnail.js";
  2. import { WebVTTThumbnailUtils } from "./WebVTTThumbnailUtils.js";
  3. import { IntervalTimeout, TimeConversion } from '../utils/helperTools.js'
  4. export { createCoverOverlay, TextOverlay, ThumbnailPositionOnElementHover, ThumbnailLoopOnImageHover, ThumbnailPositionOnElementHoverViaTimestamp, ThumbnailPositionOnImageHover, ThumbnailPositionOnSeekbarHover };
  5. /**
  6. * Create an overlay to a cover image providing media related information like duration and resolution.
  7. * @param {Object} target - Target element to attach overlay as child element.
  8. * @param {Object} dataSourceElement - Element that provides the information via data attributes.
  9. */
  10. function createCoverOverlay(target, dataSourceElement) {
  11. const div = document.createElement('div');
  12. div.className = "video-cover-text-container";
  13. div.innerHTML = '<i class="overlay-item fas fa-film"></i>';
  14. new TextOverlay(div, dataSourceElement, 'data-text-duration');
  15. new TextOverlay(div, dataSourceElement, 'data-text-resolution');
  16. target.append(div);
  17. }
  18. /**
  19. * Create a text overlay.
  20. * @author ckraemme <ckraemme@awi.de>
  21. * @private
  22. */
  23. class TextOverlay {
  24. /**
  25. * Append data attributes as text overlay to a target element.
  26. * @param {Object} target - Target element to append text overlay as child.
  27. * @param {Object} dataSourceElement - Element that contains data attributes.
  28. * @param {RegExp} dataSourceSelector - Regex expression to select the data attribute.
  29. */
  30. constructor(target, dataSourceElement, dataSourceSelector) {
  31. this.target = target;
  32. this.dataSourceElement = dataSourceElement;
  33. this.dataSourceSelector = dataSourceSelector;
  34. this.addOverlayTextElements_();
  35. }
  36. /**
  37. * Get specific data attributes in an HTML element.
  38. * @param {Object} target - Target element with data attributes.
  39. * @param {RegExp} regexSelector - Regex expression to select the data attribute.
  40. * @returns {Array} Contains the filtered attributes.
  41. */
  42. static filterAttributesByName(target, regexSelector) {
  43. const re = new RegExp(regexSelector);
  44. let collectAttributes = target.attributes;
  45. const filteredAttributes = [...collectAttributes].filter(item => re.test(item.name));
  46. return filteredAttributes
  47. }
  48. /**
  49. * For each filtered data attribute, create a overlay text.
  50. * @private
  51. */
  52. addOverlayTextElements_() {
  53. const textDataAttributes = TextOverlay.filterAttributesByName(this.dataSourceElement, this.dataSourceSelector);
  54. textDataAttributes.forEach((item) => {
  55. this.addOverlayText_(item);
  56. });
  57. }
  58. /**
  59. * Create the overlay text element and append it as child to the target element.
  60. * @param {String} item - Specific data attribute.
  61. * @private
  62. */
  63. addOverlayText_(item) {
  64. const re = new RegExp(this.dataSourceSelector);
  65. const attributeName = item.name;
  66. const attributeValue = item.value;
  67. if (attributeValue) {
  68. const textElement = document.createElement('span');
  69. textElement.className = "overlay-item " + attributeName.replace(re, 'overlay-text-');
  70. textElement.innerText = attributeValue;
  71. textElement.style.cursor = 'default';
  72. this.target.appendChild(textElement);
  73. }
  74. }
  75. }
  76. /**
  77. * Show thumbnails of a webVTT file relative to the position of an element that is hovered.
  78. * @author ckraemme <ckraemme@awi.de>
  79. * @private
  80. */
  81. class ThumbnailPositionOnElementHover {
  82. /**
  83. * Provide a mouse controlled thumbnail playback. Construct the object and attach all required event listeners to the element.
  84. * @param {Object} hoverElement - Hover element where event listeners should be attached.
  85. * @param {Object} targetElement - Target element that should contain timestamp and canvas element.
  86. * @param {Number} canvasWidth - Canvas width in pixel.
  87. * @param {Number} canvasHeight - Canvas height in pixel.
  88. * @param {String} vttUrl - URL to the webVTT file.
  89. * @param {Object} timestampTextElement - Text element to show timestamps as inner text.
  90. * @param {String} timestampTextOnPointerLeave - Timestamp text that is shown when the pointer leaves the element.
  91. * @param {Number} showNextThumbnailEveryNthPixel - Skip thumbnails that are to close in order to avoid a too sensitive mouse pointer.
  92. */
  93. constructor(hoverElement, targetElement, canvasWidth, canvasHeight, vttUrl, timestampTextElement = null, timestampTextOnPointerLeave, showNextThumbnailEveryNthPixel = 10) {
  94. this.manifestData, this.canvasElement;
  95. this.canvasWidth = canvasWidth;
  96. this.canvasHeight = canvasHeight;
  97. this.vttUrl = vttUrl;
  98. this.targetElement = targetElement;
  99. this.showNextThumbnailEveryNthPixel = showNextThumbnailEveryNthPixel;
  100. this.stateToPreventDuplicateBuilds = 0;
  101. this.hoverElement = hoverElement;
  102. this.timestampTextElement = timestampTextElement;
  103. this.timestampTextOnPointerLeave = timestampTextOnPointerLeave;
  104. this.hoverElement.addEventListener('pointerenter', this.coverPointerEnter_.bind(this));
  105. this.hoverElement.addEventListener('pointerleave', this.coverPointerLeave_.bind(this));
  106. }
  107. /**
  108. * Read the manifest file and attach an event lister when the element is hovered.
  109. * @param {Event} event - Pointerenter event.
  110. * @private
  111. */
  112. coverPointerEnter_(event) {
  113. if (this.stateToPreventDuplicateBuilds === 0) {
  114. this.stateToPreventDuplicateBuilds = 1;
  115. let webVTT = new WebVTTThumbnail(this.vttUrl);
  116. webVTT.init(-1, false).then(() => {
  117. this.manifestData = webVTT.manifestData;
  118. this.coverPointerHover_(event);
  119. this.pointerMoveListener = this.coverPointerHover_.bind(this);
  120. this.hoverElement.addEventListener('pointermove', this.pointerMoveListener);
  121. })
  122. }
  123. }
  124. /**
  125. * Create or update the thumbnail frame in the canvas and timestamp.
  126. * @param {Event} event - Pointermove event.
  127. * @private
  128. */
  129. coverPointerHover_(event) {
  130. const reducedManifestObjectNumber = Math.round(event.target.clientWidth / this.showNextThumbnailEveryNthPixel);
  131. const manifestDataReduced = WebVTTThumbnailUtils.reduceManifestEvenlyDistributed(this.manifestData, reducedManifestObjectNumber);
  132. const [manifestDataAtTimestamp,] = WebVTTThumbnailUtils.getManifestObjectOnHover(event, manifestDataReduced);
  133. this.generateThumbnail_(manifestDataAtTimestamp, manifestDataAtTimestamp['time_start']);
  134. }
  135. generateThumbnail_(manifestDataAtTimestamp, timestampTextElementText) {
  136. if (typeof this.canvasElement == 'undefined')
  137. this.canvasElement = ThumbnailCanvas.createThumbnailCanvas(this.targetElement);
  138. if (this.timestampTextElement != null)
  139. this.timestampTextElement.innerText = timestampTextElementText;
  140. ThumbnailCanvas.paintThumbnail(manifestDataAtTimestamp, this.canvasElement, this.canvasWidth, this.canvasHeight);
  141. }
  142. /**
  143. * Remove all attached elements and events.
  144. * @param {Event} event - Pointerleave event.
  145. * @private
  146. */
  147. coverPointerLeave_(event) {
  148. if (this.stateToPreventDuplicateBuilds === 1) {
  149. if (ThumbnailCanvas.removeThumbnailCanvas(this.targetElement)) {
  150. this.canvasElement = undefined;
  151. this.hoverElement.removeEventListener('pointermove', this.pointerMoveListener);
  152. if (this.timestampTextElement != null && this.timestampTextOnPointerLeave != null)
  153. this.timestampTextElement.innerText = this.timestampTextOnPointerLeave;
  154. this.stateToPreventDuplicateBuilds = 0;
  155. }
  156. else
  157. setTimeout(() => { this.coverPointerLeave_(event) }, 100);
  158. }
  159. }
  160. }
  161. /**
  162. * Overwrite the default ThumbnailPositionOnElementHover class and replace the referenced element.
  163. * Here, the seek bar is used to show thumbnails referenced in a webVTT file. It is based on the position of a pointer that hovers the seek bar.
  164. * @extends ThumbnailPositionOnElementHover
  165. * @author ckraemme <ckraemme@awi.de>
  166. */
  167. class ThumbnailPositionOnSeekbarHover extends ThumbnailPositionOnElementHover {
  168. /**
  169. * Note: This class is based on class inheritance.
  170. * Only new initialized or removed parameters are described here.
  171. * Observe: showNextThumbnailEveryNthPixel can be omitted, as it is not used in the coverPointerHover_() method defined here.
  172. */
  173. constructor(hoverElement, targetElement, canvasWidth, canvasHeight, vttUrl, timestampTextElement = null, timestampTextOnPointerLeave) {
  174. super(hoverElement, targetElement, canvasWidth, canvasHeight, vttUrl, timestampTextElement, timestampTextOnPointerLeave);
  175. }
  176. /**
  177. * Overwrites ThumbnailPositionOnElementHover.coverPointerHover_() due to the required seek bar specificity.
  178. * @param {Event} event - Pointermove event.
  179. * @private
  180. */
  181. coverPointerHover_(event) {
  182. const isSeekbar = true;
  183. const [manifestDataAtTimestamp, currentTimeHHMMSSms] = WebVTTThumbnailUtils.getManifestObjectOnHover(event, this.manifestData, isSeekbar);
  184. this.generateThumbnail_(manifestDataAtTimestamp, currentTimeHHMMSSms.split('.')[0]);
  185. }
  186. }
  187. /**
  188. * Overwrite the default ThumbnailPositionOnElementHover class and replace the referenced element.
  189. * Here, a time element is used to show thumbnails referenced in a webVTT file. It is based on the inner text of an html element that indicates the time when hovering the seek bar.
  190. * @extends ThumbnailPositionOnElementHover
  191. * @author ckraemme <ckraemme@awi.de>
  192. */
  193. class ThumbnailPositionOnElementHoverViaTimestamp extends ThumbnailPositionOnElementHover {
  194. /**
  195. * Note: This class is based on class inheritance.
  196. * Only new initialized or removed parameters are described here.
  197. * Observe: showNextThumbnailEveryNthPixel can be omitted, as it is not used in the coverPointerHover_() method defined here.
  198. * @param {Object} timeElementSelector - Selector of time element that contains the timestamp as innerText.
  199. */
  200. constructor(hoverElement, targetElement, timeElementSelector, canvasWidth, canvasHeight, vttUrl, timestampTextElement = null, timestampTextOnPointerLeave) {
  201. // observe: timeElementSelector as additional input argument
  202. super(hoverElement, targetElement, canvasWidth, canvasHeight, vttUrl, timestampTextElement, timestampTextOnPointerLeave);
  203. this.timeElementSelector = timeElementSelector;
  204. }
  205. /**
  206. * Overwrites ThumbnailPositionOnElementHover.coverPointerHover_() due to the required seek bar specificity.
  207. * @param {Event} event - Pointermove event.
  208. * @private
  209. */
  210. coverPointerHover_(event) {
  211. const delayReadingOfTimeElement_ms = 20; // wait until the time element changed - could be replaced by a mutation observer in the future
  212. setTimeout(() => {
  213. const currentTimeHHMMSS = document.querySelector(this.timeElementSelector)?.innerText;
  214. const currentTimeHHMMSSms = TimeConversion.time2IsoDayTime(currentTimeHHMMSS)
  215. const manifestDataAtTimestamp = WebVTTThumbnailUtils.getManifestObjectViaTimestamp(currentTimeHHMMSSms, this.manifestData);
  216. this.generateThumbnail_(manifestDataAtTimestamp, currentTimeHHMMSSms.split('.')[0]);
  217. }, delayReadingOfTimeElement_ms);
  218. }
  219. }
  220. /**
  221. * Show thumbnails referenced in a webVTT file relative to the hover position of an image element.
  222. * @extends ThumbnailPositionOnElementHover
  223. * @author ckraemme <ckraemme@awi.de>
  224. */
  225. class ThumbnailPositionOnImageHover extends ThumbnailPositionOnElementHover {
  226. /**
  227. * Provide a mouse controlled thumbnail playback. Construct the object and attach all required event listeners to the image element.
  228. * @param {Object} imageElement - Image element where event listeners should be attached.
  229. * @param {String} imageElementDataAttributeWebVTTUrl - Name of the data attribute in the image element.
  230. * @param {Object} timestampTextElement - Text element to show timestamps as inner text.
  231. * @param {String} timestampTextOnPointerLeave - Timestamp text that is shown when the pointer leaves the element.
  232. */
  233. constructor(imageElement, imageElementDataAttributeWebVTTUrl = "data-src-webvtt", timestampTextElement = null, timestampTextOnPointerLeave) {
  234. const vttUrl = imageElement.getAttribute(imageElementDataAttributeWebVTTUrl);
  235. const imageContainer = imageElement.parentElement;
  236. const coverContainer = document.createElement('div');
  237. coverContainer.className = 'cover-container';
  238. imageContainer.appendChild(coverContainer);
  239. const canvasWidth = imageElement.width;
  240. const canvasHeight = imageElement.height;
  241. const hoverElement = coverContainer;
  242. super(hoverElement, coverContainer, canvasWidth, canvasHeight, vttUrl, timestampTextElement, timestampTextOnPointerLeave);
  243. }
  244. }
  245. /**
  246. * Play thumbnails referenced in a webVTT file in a loop on top of an image element that is hovered.
  247. * @author ckraemme <ckraemme@awi.de>
  248. */
  249. class ThumbnailLoopOnImageHover {
  250. /**
  251. * Create a canvas element on top of an image element where thumbnails are shown when the image is hovered.
  252. * @param {Object} imageElement - Image element that provides the url to the webVTT file as data attribute.
  253. * @param {string} imageElementDataAttributeWebVTTUrl - Name of the data attribute in the image element.
  254. * @param {Number} timeIntervalMilliseconds - Time interval between thumbnail images. Allows playback speed control.
  255. */
  256. constructor(imageElement, imageElementDataAttributeWebVTTUrl = "data-src-webvtt", timeIntervalMilliseconds = 100) {
  257. this.playback;
  258. this.timeIntervalMilliseconds = timeIntervalMilliseconds;
  259. this.stateToPreventDuplicateBuilds = 0;
  260. this.imageElement = imageElement;
  261. this.vttUrl = imageElement.getAttribute(imageElementDataAttributeWebVTTUrl);
  262. const coverContainer = imageElement.parentElement;
  263. coverContainer.addEventListener('mouseenter', this.coverMouseEnter_.bind(this));
  264. coverContainer.addEventListener('mouseleave', this.coverMouseLeave_.bind(this));
  265. }
  266. /**
  267. * Play a loop of thumbnail images provided via a webVTT file.
  268. * @param {Object} imageElement - Image element where thumbnail loop is attached as sibling.
  269. * @param {String} vttUrl - URL to the webVTT file.
  270. * @param {Number} currentFrame - Current thumbnail frame. Starts with zero.
  271. * @param {Number} timeIntervalMilliseconds - Time interval between thumbnail images. Allows playback speed control.
  272. * @param {Boolean} reversePlayback - If true play thumbnails in reverse order.
  273. * @param {Object} timestampTextElement - Text element to show timestamps as inner text.
  274. * @param {String} timestampTextOnRemove - Timestamp text that is shown when the thumbnail loop is removed.
  275. */
  276. static playThumbnailLoop(imageElement, vttUrl, currentFrame = 0, timeIntervalMilliseconds = 100, reversePlayback = false, timestampTextElement = null, timestampTextOnRemove) {
  277. let webVTT = new WebVTTThumbnail(vttUrl);
  278. return webVTT.init(-1, false).then(() => {
  279. return new ThumbnailLoop(imageElement.parentElement, webVTT.manifestData, imageElement.width, imageElement.height, currentFrame, timeIntervalMilliseconds, reversePlayback, timestampTextElement, timestampTextOnRemove);
  280. });
  281. }
  282. /**
  283. * Start thumbnail playback loop.
  284. * @param {Event} event - Pointerenter event.
  285. * @private
  286. */
  287. coverMouseEnter_(event) {
  288. if (this.stateToPreventDuplicateBuilds === 0) {
  289. this.stateToPreventDuplicateBuilds = 1;
  290. const currentFrame = 0;
  291. ThumbnailLoopOnImageHover.playThumbnailLoop(this.imageElement, this.vttUrl, currentFrame, this.timeIntervalMilliseconds).then(playback => this.playback = playback);
  292. }
  293. }
  294. /**
  295. * Stop and destroy thumbnail playback loop.
  296. * @param {Event} event - Pointerleave event.
  297. * @private
  298. */
  299. coverMouseLeave_(event) {
  300. if (this.stateToPreventDuplicateBuilds === 1) {
  301. if (typeof this.playback !== 'undefined') {
  302. this.playback.remove();
  303. this.stateToPreventDuplicateBuilds = 0;
  304. }
  305. else
  306. setTimeout(() => { this.coverMouseLeave_(event) }, 1000);
  307. }
  308. }
  309. }
  310. /**
  311. * Create an thumbnail loop and provide methods for controlling the playback.
  312. * @author ckraemme <ckraemme@awi.de>
  313. * @private
  314. */
  315. class ThumbnailLoop {
  316. /**
  317. * Create a thumbnail loop playback.
  318. * @param {Object} targetElement - Target element.
  319. * @param {Object} manifestData - Manifest data object providing all thumbnail related information.
  320. * @param {Number} containerWidth - Container width in pixel.
  321. * @param {Number} containerHeight - Container height in pixel.
  322. * @param {Number} currentFrame - Current thumbnail frame. Starts with zero.
  323. * @param {Number} timeIntervalMilliseconds - Time interval between thumbnail images. Allows playback speed control.
  324. * @param {Boolean} reversePlayback - If true play thumbnails in reverse order.
  325. * @param {Object} timestampTextElement - Text element to show timestamps as inner text.
  326. * @param {String} timestampTextOnRemove - Timestamp text that is shown when the thumbnail loop is removed.
  327. */
  328. constructor(targetElement, manifestData, containerWidth, containerHeight, currentFrame = 0, timeIntervalMilliseconds = 100, reversePlayback = false, timestampTextElement = null, timestampTextOnRemove) {
  329. this.targetElement = targetElement;
  330. this.manifestData = manifestData;
  331. this.containerWidth = containerWidth;
  332. this.containerHeight = containerHeight;
  333. this.currentFrame = currentFrame;
  334. this.timeIntervalMilliseconds = timeIntervalMilliseconds;
  335. this.reversePlayback = reversePlayback;
  336. this.timestampTextElement = timestampTextElement;
  337. this.timestampTextOnRemove = timestampTextOnRemove;
  338. this.canvasElement = ThumbnailCanvas.createThumbnailCanvas(targetElement);
  339. this.intervalTimer = new IntervalTimeout(this.looping_.bind(this), timeIntervalMilliseconds);
  340. this.intervalTimer.setInterval();
  341. }
  342. /**
  343. * Create the thumbnail playback loop effect by controlling and painting the frames.
  344. * @private
  345. */
  346. looping_() {
  347. this.intervalTimer.delayMS = this.timeIntervalMilliseconds;
  348. const boundary = false;
  349. if (this.reversePlayback === false)
  350. this.skipFrame(1, boundary);
  351. else
  352. this.skipFrame(-1, boundary);
  353. this.paintCurrentFrame();
  354. }
  355. /**
  356. * Paint current thumbnail frame to the canvas.
  357. */
  358. paintCurrentFrame() {
  359. ThumbnailCanvas.paintThumbnail(this.manifestData[this.currentFrame], this.canvasElement, this.containerWidth, this.containerHeight);
  360. if (this.timestampTextElement != null)
  361. this.timestampTextElement.innerText = this.manifestData[this.currentFrame]['time_start'];
  362. }
  363. /**
  364. * Pause thumbnail loop playback.
  365. */
  366. pause() {
  367. this.intervalTimer.clearInterval();
  368. this.intervalTimer.timeoutID = null;
  369. }
  370. /**
  371. * Pause thumbnail loop playback.
  372. * @param {Number} numberOfFrames - Amount of frames that should be skipped starting from the current playback position (can be positive and negative).
  373. * @param {Boolean} boundary - If true, limit the amount of skipped frames to the number of available frames. Else, start at the other end like in a loop.
  374. */
  375. skipFrame(numberOfFrames, boundary = true) {
  376. var newPosition;
  377. if (boundary) {
  378. const firstBoundaryValue = Math.max(0, this.currentFrame + numberOfFrames);
  379. newPosition = Math.min(firstBoundaryValue, this.manifestData.length - 1)
  380. }
  381. else {
  382. newPosition = (this.currentFrame + numberOfFrames) % this.manifestData.length
  383. if (newPosition < 0)
  384. newPosition = this.manifestData.length + newPosition;
  385. }
  386. this.currentFrame = newPosition;
  387. }
  388. /**
  389. * Resume thumbnail loop playback.
  390. */
  391. resume() {
  392. if (this.intervalTimer.timeoutID == null)
  393. this.intervalTimer.setInterval();
  394. }
  395. /**
  396. * Remove thumbnail loop object containing all child elements.
  397. * @param {Boolean} removeTargetCompletely - Indicates to remove target element too.
  398. */
  399. remove(removeTargetCompletely = false) {
  400. if (ThumbnailCanvas.removeThumbnailCanvas(this.targetElement)) {
  401. if (removeTargetCompletely)
  402. this.targetElement.remove();
  403. this.intervalTimer.clearInterval();
  404. if (this.timestampTextElement != null && this.timestampTextOnRemove != null)
  405. this.timestampTextElement.innerText = this.timestampTextOnRemove;
  406. }
  407. else
  408. setTimeout(() => { this.remove(removeTargetCompletely) }, 100);
  409. }
  410. }
  411. /**
  412. * Class that provides tools to create, paint images on and remove canvas in the context of the webVTT thumbnails.
  413. * @author ckraemme <ckraemme@awi.de>
  414. */
  415. class ThumbnailCanvas {
  416. /**
  417. * Create canvas element, set default canvas settings and append it to a target element.
  418. * @param {Object} targetElement - Target element to append canvas as child.
  419. * @returns {Object} Canvas element.
  420. */
  421. static createThumbnailCanvas(targetElement) {
  422. const canvasElement = document.createElement('canvas');
  423. const context = canvasElement.getContext('2d');
  424. context.imageSmoothingEnabled = true;
  425. context.imageSmoothingQuality = "high";
  426. targetElement.append(canvasElement);
  427. return canvasElement
  428. }
  429. /**
  430. * Remove the first canvas element found in a target element.
  431. * @param {Object} targetElement - Target element that contain a canvas.
  432. * @returns {Boolean} Return true when canvas could be removed, else false.
  433. */
  434. static removeThumbnailCanvas(targetElement) {
  435. const canvas = targetElement.querySelector('canvas');
  436. if (canvas)
  437. canvas.remove();
  438. else
  439. return false
  440. return true
  441. }
  442. /**
  443. * Paint the image from a thumbnail object to a canvas element.
  444. * @param {Object} thumbnailObject - Thumbnail object, i.e., Object { time_start: "00:00:00.000", time_end: "00:00:00.280", data: "thumb.jpeg#xywh=0,0,250,141", url: "https://localhost/Desktop/marine-data/thumbnail-hover/thumb.jpeg", image: img }.
  445. * @param {Object} canvasElement - Target canvas element.
  446. * @param {Object} canvasWidth - Canvas width in pixel.
  447. * @param {Object} canvasHeight - Canvas height in pixel.
  448. */
  449. static paintThumbnail(thumbnailObject, canvasElement, canvasWidth, canvasHeight) {
  450. const imageSprite = thumbnailObject.data.includes('#xywh')
  451. if (imageSprite)
  452. var [x, y, frameWidth, frameHeight] = thumbnailObject.data.split('#xywh=')[1].split(',');
  453. else
  454. var [x, y, frameWidth, frameHeight] = [0, 0, thumbnailObject.image.width, thumbnailObject.image.height];
  455. if (canvasHeight === undefined)
  456. canvasHeight = canvasWidth * frameHeight / frameWidth;
  457. if (canvasWidth === undefined)
  458. canvasWidth = canvasHeight * frameWidth / frameHeight;
  459. canvasElement.width = canvasWidth;
  460. canvasElement.height = canvasHeight;
  461. const context = canvasElement.getContext('2d');
  462. context.clearRect(0, 0, canvasElement.width, canvasElement.height);
  463. context.drawImage(thumbnailObject.image, x, y, frameWidth, frameHeight, 0, 0, canvasWidth, canvasHeight);
  464. }
  465. }