paste-image.pl 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #! /usr/bin/perl
  2. # Copyright (C) 2017 Alex Schroeder <alex@gnu.org>
  3. # This program is free software: you can redistribute it and/or modify it under
  4. # the terms of the GNU General Public License as published by the Free Software
  5. # Foundation, either version 3 of the License, or (at your option) any later
  6. # version.
  7. #
  8. # This program is distributed in the hope that it will be useful, but WITHOUT
  9. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  10. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License along with
  13. # this program. If not, see <http://www.gnu.org/licenses/>.
  14. use strict;
  15. use v5.10;
  16. AddModuleDescription('paste-image.pl', 'Paste Files to Upload');
  17. our (@MyInitVariables, $ScriptName, $HtmlHeaders, $MaxPost, $CommentsPattern,
  18. $QuestionaskerSecretKey);
  19. our ($PasteImageOnBrowse);
  20. $PasteImageOnBrowse = 0;
  21. push(@MyInitVariables, \&PasteImageScript);
  22. # Resampling based on the following:
  23. # https://stackoverflow.com/a/19223362/534893
  24. # https://github.com/viliusle/Hermite-resize
  25. sub PasteImageScript {
  26. my $id = GetId();
  27. return unless $id;
  28. OpenPage($id);
  29. my $username = GetParam('username', '');
  30. my $templatePage = "Image_{n}_for_$id";
  31. my $templateText = "Image {n}";
  32. my $question = $QuestionaskerSecretKey || 'question';
  33. if ((GetParam('action', 'browse') eq 'edit'
  34. or GetParam('action', 'browse') eq 'new'
  35. or $CommentsPattern and $id =~ /$CommentsPattern/
  36. or $PasteImageOnBrowse and GetParam('action', 'browse') eq 'browse')
  37. and $HtmlHeaders !~ /PasteImage/) {
  38. $HtmlHeaders .= << "EOT";
  39. <script type="text/javascript">
  40. if (!HTMLTextAreaElement.prototype.insertAtCaret) {
  41. HTMLTextAreaElement.prototype.insertAtPoint = function (text) {
  42. text = text || '';
  43. if (this.selectionStart || this.selectionStart === 0) {
  44. // Others
  45. var startPos = this.selectionStart;
  46. var endPos = this.selectionEnd;
  47. this.value = this.value.substring(0, startPos) +
  48. text +
  49. this.value.substring(endPos, this.value.length);
  50. this.selectionStart = startPos + text.length;
  51. this.selectionEnd = startPos + text.length;
  52. } else {
  53. this.value += text;
  54. }
  55. };
  56. };
  57. var PasteImage = {
  58. init: function() {
  59. let e = document.getElementById('text') || document.getElementById('aftertext');
  60. if (e)
  61. e.addEventListener('paste', PasteImage.handler);
  62. },
  63. handler: function(e) {
  64. // Chrome
  65. if (e.clipboardData) {
  66. let items = e.clipboardData.items;
  67. for (var i = 0; i < items.length; i++) {
  68. if (items[i].type.indexOf("image") !== -1) {
  69. let blob = items[i].getAsFile();
  70. let reader = new window.FileReader();
  71. reader.onloadend = function() {
  72. let dataUrl = reader.result;
  73. let n = 1;
  74. while (n++ < 4 && $MaxPost > 0 && dataUrl.length > $MaxPost)
  75. dataUrl = PasteImage.shrink(dataUrl);
  76. PasteImage.process(dataUrl, "$templatePage", "$templateText", 1);
  77. }
  78. reader.readAsDataURL(blob);
  79. }
  80. }
  81. }
  82. },
  83. shrink: function(dataUrl) {
  84. let image = new Image;
  85. image.src = dataUrl;
  86. let canvas = document.createElement("canvas");
  87. canvas.width = image.width;
  88. canvas.height = image.height;
  89. let ctx = canvas.getContext("2d");
  90. ctx.drawImage(image, 0, 0);
  91. let width_source = canvas.width;
  92. let height_source = canvas.height;
  93. let width = Math.round(width_source * 0.5);
  94. let height = Math.round(height_source * 0.5);
  95. let ratio_w = width_source / width;
  96. let ratio_h = height_source / height;
  97. let ratio_w_half = Math.ceil(ratio_w / 2);
  98. let ratio_h_half = Math.ceil(ratio_h / 2);
  99. let img = ctx.getImageData(0, 0, width_source, height_source);
  100. let img2 = ctx.createImageData(width, height);
  101. let data = img.data;
  102. let data2 = img2.data;
  103. for (let j = 0; j < height; j++) {
  104. for (let i = 0; i < width; i++) {
  105. let x2 = (i + j * width) * 4;
  106. let weight = 0;
  107. let weights = 0;
  108. let weights_alpha = 0;
  109. let gx_r = 0;
  110. let gx_g = 0;
  111. let gx_b = 0;
  112. let gx_a = 0;
  113. let center_y = (j + 0.5) * ratio_h;
  114. let yy_start = Math.floor(j * ratio_h);
  115. let yy_stop = Math.ceil((j + 1) * ratio_h);
  116. for (let yy = yy_start; yy < yy_stop; yy++) {
  117. let dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
  118. let center_x = (i + 0.5) * ratio_w;
  119. let w0 = dy * dy; //pre-calc part of w
  120. let xx_start = Math.floor(i * ratio_w);
  121. let xx_stop = Math.ceil((i + 1) * ratio_w);
  122. for (let xx = xx_start; xx < xx_stop; xx++) {
  123. let dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
  124. let w = Math.sqrt(w0 + dx * dx);
  125. if (w >= 1) {
  126. //pixel too far
  127. continue;
  128. }
  129. //hermite filter
  130. weight = 2 * w * w * w - 3 * w * w + 1;
  131. let pos_x = 4 * (xx + yy * width_source);
  132. //alpha
  133. gx_a += weight * data[pos_x + 3];
  134. weights_alpha += weight;
  135. //colors
  136. if (data[pos_x + 3] < 255)
  137. weight = weight * data[pos_x + 3] / 250;
  138. gx_r += weight * data[pos_x];
  139. gx_g += weight * data[pos_x + 1];
  140. gx_b += weight * data[pos_x + 2];
  141. weights += weight;
  142. }
  143. }
  144. data2[x2] = gx_r / weights;
  145. data2[x2 + 1] = gx_g / weights;
  146. data2[x2 + 2] = gx_b / weights;
  147. data2[x2 + 3] = gx_a / weights_alpha;
  148. }
  149. }
  150. canvas.width = width;
  151. canvas.height = height;
  152. ctx.putImageData(img2, 0, 0);
  153. let png = canvas.toDataURL();
  154. let jpg = canvas.toDataURL('image/jpeg');
  155. return png <= jpg ? png : jpg;
  156. },
  157. process: function(dataUrl, templatePage, templateText, n) {
  158. let name = templatePage.replace('{n}', n);
  159. let text = templateText.replace('{n}', n);
  160. let xhr = new XMLHttpRequest();
  161. xhr.open("HEAD", "$ScriptName/" + name, true);
  162. xhr.onreadystatechange = function() {
  163. if (xhr.readyState == 4) {
  164. if (xhr.status == 200) {
  165. PasteImage.process(dataUrl, templatePage, templateText, n+1);
  166. } else if (xhr.status == 404) {
  167. PasteImage.post(dataUrl, name, text);
  168. } else {
  169. let re = /<h1>(.*)<\\/h1>/g;
  170. let match = re.exec(xhr.responseText);
  171. alert(match[1]);
  172. }
  173. }
  174. };
  175. xhr.send(null);
  176. },
  177. post: function(dataUrl, name, text) {
  178. let xhr = new XMLHttpRequest();
  179. xhr.open("POST", "$ScriptName", true);
  180. xhr.onreadystatechange = function() {
  181. if (xhr.readyState == 4) {
  182. if (xhr.status == 200) {
  183. let e = document.getElementById('text') || document.getElementById('aftertext');
  184. e.insertAtPoint("[[image:" + name + "|" + text + "]]");
  185. } else {
  186. let re = /<h1>(.*)<\\/h1>/g;
  187. let match = re.exec(xhr.responseText);
  188. alert(match[1]);
  189. }
  190. }
  191. }
  192. let mimeType = dataUrl.split(',')[0].split(':')[1].split(';')[0];
  193. let content = encodeURIComponent(dataUrl.split(',')[1]);
  194. let params = "title=" + encodeURIComponent(name);
  195. params += "&summary=" + encodeURIComponent(name);
  196. params += "&username=" + encodeURIComponent("$username");
  197. params += "&recent_edit=on";
  198. params += "&$question=1";
  199. params += "&text=#FILE " + mimeType + "%0A" + content;
  200. xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  201. xhr.send(params);
  202. },
  203. };
  204. window.addEventListener('load', PasteImage.init);
  205. </script>
  206. EOT
  207. }
  208. }