1. <code class="language-html"><html>
  2. <head>
  3. <style type="text/css">
  4. form, input {width: 73px;height: 27px;}
  5. form {
  6. position: relative;
  7. float: left;
  8. margin: 0 10px 0 0;
  9. }
  10. #up-button{
  11. position: absolute;
  12. right: 0;
  13. top: 0;
  14. cursor: pointer;
  15. opacity: 0;
  16. filter: alpha(opacity=0);
  17. outline: none;
  18. }
  19. #button{
  20. }
  21. iframe {display: none;}
  22. </style>
  23. </head>
  24. <body>
  25. <div class="bt">
  26. <form id="uf">
  27. <input type="file" name="file" id="up-button"/>
  28. <input type="button" id="button" value="upload"/>
  29. <input type="button" id="download" value="download"/>
  30. </form>
  31. <span><input type="radio" value="spread" id="spread" name="filter"/><label for="spread">油画效果</label></span>
  32. <span><input type="radio" id="gray" name="filter"/><label for="gray">灰度效果</label></span>
  33. <span><input type="radio" id="comic" name="filter"/><label for="comic">连环画效果</label></span>
  34. <span><input type="radio" id="old" name="filter"/><label for="old">怀旧效果</label></span>
  35. <span><input type="radio" id="negatives" name="filter"/><label for="negatives">底片效果</label></span>
  36. <span><input type="radio" id="black" name="filter"/><label for="black">黑白效果</label></span>
  37. <span><input type="radio" id="cameo" name="filter"/><label for="cameo">浮雕效果</label></span>
  38. </div>
  39. <br>
  40. <canvas id="cv">fuck ie</canvas>
  41. <canvas id="myCanvas" >Gray Filter</canvas>
  42. <script>
  43. /**
  44. * 获取mimeType
  45. * @param  {String} type the old mime-type
  46. * @return the new mime-type
  47. */
  48. var _fixType = function(type) {
  49. type = type.toLowerCase().replace(/jpg/i, 'jpeg');
  50. var r = type.match(/png|jpeg|bmp|gif/)[0];
  51. return 'image/' + r;
  52. };
  53. /**
  54. * 在本地进行文件保存
  55. * @param  {String} data     要保存到本地的图片数据
  56. * @param  {String} filename 文件名
  57. */
  58. var saveFile = function(data, filename){
  59. var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
  60. save_link.href = data;
  61. save_link.download = filename;
  62. var event = document.createEvent('MouseEvents');
  63. event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  64. save_link.dispatchEvent(event);
  65. };
  66. document.getElementById("download").onclick=function()
  67. {
  68. //图片导出为 png 格式
  69. var type = 'png';
  70. var imgData = canvas.toDataURL(type);
  71. // 加工image data,替换mime type
  72. imgData = imgData.replace(_fixType(type),'image/octet-stream');
  73. // 下载后的问题名
  74. var filename = 'bloglaotou_' + (new Date()).getTime() + '.' + type;
  75. // download
  76. saveFile(imgData,filename);
  77. }
  78. //  1.灰度效果
  79. //计算公式 .299 * r + .587 * g + .114 * b;
  80. // calculate gray scale value
  81. function gray(canvasData)
  82. {
  83. for ( var x = 0; x < canvasData.width; x++) {
  84. for ( var y = 0; y < canvasData.height; y++) {
  85. // Index of the pixel in the array
  86. var idx = (x + y * canvasData.width) * 4;
  87. var r = canvasData.data[idx + 0];
  88. var g = canvasData.data[idx + 1];
  89. var b = canvasData.data[idx + 2];
  90. var gray = .299 * r + .587 * g + .114 * b;
  91. // assign gray scale value
  92. canvasData.data[idx + 0] = gray; // Red channel
  93. canvasData.data[idx + 1] = gray; // Green channel
  94. canvasData.data[idx + 2] = gray; // Blue channel
  95. canvasData.data[idx + 3] = 255; // Alpha channel
  96. // add black border
  97. if(x < 8 || y < 8 || x > (canvasData.width - 8) || y > (canvasData.height - 8))
  98. {
  99. canvasData.data[idx + 0] = 0;
  100. canvasData.data[idx + 1] = 0;
  101. canvasData.data[idx + 2] = 0;
  102. }
  103. }
  104. }
  105. return canvasData;
  106. }
  107. //2.怀旧效果
  108. function old(canvasData)
  109. {
  110. for ( var x = 0; x < canvasData.width; x++) {
  111. for ( var y = 0; y < canvasData.height; y++) {
  112. // Index of the pixel in the array
  113. var idx = (x + y * canvasData.width) * 4;
  114. var r = canvasData.data[idx + 0];
  115. var g = canvasData.data[idx + 1];
  116. var b = canvasData.data[idx + 2];
  117. var dr=.393*r+.769*g+.189*b;
  118. var dg=.349*r+.686*g+.168*b;
  119. var db=.272*r+.534*g+.131*b;
  120. var scale=Math.random()*0.5 + 0.5;
  121. var fr=scale*dr+(1-scale)*r;
  122. scale=Math.random()*0.5 + 0.5;
  123. var fg=scale*dg+(1-scale)*g;
  124. scale=Math.random()*0.5 + 0.5;
  125. var fb=scale*db+(1-scale)*b;
  126. canvasData.data[idx + 0] = fr; // Red channel
  127. canvasData.data[idx + 1] = fg; // Green channel
  128. canvasData.data[idx + 2] = fb; // Blue channel
  129. canvasData.data[idx + 3] = 255; // Alpha channel
  130. // add black border
  131. if(x < 8 || y < 8 || x > (canvasData.width - 8) || y > (canvasData.height - 8))
  132. {
  133. canvasData.data[idx + 0] = 0;
  134. canvasData.data[idx + 1] = 0;
  135. canvasData.data[idx + 2] = 0;
  136. }
  137. }
  138. }
  139. return canvasData;
  140. }
  141. //3 底片效果
  142. //算法原理:将当前像素点的RGB值分别与255之差后的值作为当前点的RGB值,即
  143. //R = 255 – R;G = 255 – G;B = 255 – B;
  144. function negatives(canvasData)
  145. {
  146. for ( var x = 0; x < canvasData.width; x++) {
  147. for ( var y = 0; y < canvasData.height; y++) {
  148. // Index of the pixel in the array
  149. var idx = (x + y * canvasData.width) * 4;
  150. var r = canvasData.data[idx + 0];
  151. var g = canvasData.data[idx + 1];
  152. var b = canvasData.data[idx + 2];
  153. var fr=255-r;
  154. var fg=255-g;
  155. var fb=255-b;
  156. canvasData.data[idx + 0] = fr; // Red channel
  157. canvasData.data[idx + 1] = fg; // Green channel
  158. canvasData.data[idx + 2] = fb; // Blue channel
  159. canvasData.data[idx + 3] = 255; // Alpha channel
  160. // add black border
  161. if(x < 8 || y < 8 || x > (canvasData.width - 8) || y > (canvasData.height - 8))
  162. {
  163. canvasData.data[idx + 0] = 0;
  164. canvasData.data[idx + 1] = 0;
  165. canvasData.data[idx + 2] = 0;
  166. }
  167. }
  168. }
  169. return canvasData;
  170. }
  171. //4 黑白效果
  172. //求RGB平均值Avg = (R + G + B) / 3,如果Avg >= 100,则新的颜色值为R=G=B=255;
  173. //如果Avg < 100,则新的颜色值为R=G=B=0;255就是白色,0就是黑色;
  174. //至于为什么用100作比较,这是一个经验值吧,设置为128也可以,可以根据效果来调整。
  175. function black(canvasData)
  176. {
  177. for ( var x = 0; x < canvasData.width; x++) {
  178. for ( var y = 0; y < canvasData.height; y++) {
  179. // Index of the pixel in the array
  180. var idx = (x + y * canvasData.width) * 4;
  181. var r = canvasData.data[idx + 0];
  182. var g = canvasData.data[idx + 1];
  183. var b = canvasData.data[idx + 2];
  184. if((r+g+b)>=300)
  185. {
  186. fr=fg=fb=255;
  187. }
  188. else
  189. {
  190. fr=fg=fb=0;
  191. }
  192. canvasData.data[idx + 0] = fr; // Red channel
  193. canvasData.data[idx + 1] = fg; // Green channel
  194. canvasData.data[idx + 2] = fb; // Blue channel
  195. canvasData.data[idx + 3] = 255; // Alpha channel
  196. // add black border
  197. if(x < 8 || y < 8 || x > (canvasData.width - 8) || y > (canvasData.height - 8))
  198. {
  199. canvasData.data[idx + 0] = 0;
  200. canvasData.data[idx + 1] = 0;
  201. canvasData.data[idx + 2] = 0;
  202. }
  203. }
  204. }
  205. return canvasData;
  206. }
  207. //5 浮雕效果
  208. //用相邻点的RGB值减去当前点的RGB值并加上128作为新的RGB值。
  209. //由于图片中相邻点的颜色值是比较接近的,因此这样的算法处理之后,只有颜色的边沿区域,
  210. //也就是相邻颜色差异较大的部分的结果才会比较明显,而其他平滑区域则值都接近128左右,
  211. //也就是灰色,这样就具有了浮雕效果。
  212. //在实际的效果中,这样处理后,有些区域可能还是会有”彩色”的一些点或者条状痕迹,所以最好再对新的RGB值做一个灰度处理。
  213. function cameo(canvasData)
  214. {
  215. for ( var x = 0; x < canvasData.width; x++) {
  216. for ( var y = 0; y < canvasData.height; y++) {
  217. // Index of the pixel in the array
  218. var idx = (x + y * canvasData.width) * 4;
  219. var r = canvasData.data[idx + 0];
  220. var g = canvasData.data[idx + 1];
  221. var b = canvasData.data[idx + 2];
  222. var idx2 = (x + (y+1) * canvasData.width) * 4;
  223. var r2 = canvasData.data[idx2 + 0];
  224. var g2 = canvasData.data[idx2 + 1];
  225. var b2 = canvasData.data[idx2 + 2];
  226. var fr=r2-r+128;
  227. var fg=g2-g+128;
  228. var fb=b2-b+128;
  229. var gray = .299 * fr + .587 * fg + .114 * fb;
  230. canvasData.data[idx + 0] = gray; // Red channel
  231. canvasData.data[idx + 1] = gray; // Green channel
  232. canvasData.data[idx + 2] = gray; // Blue channel
  233. canvasData.data[idx + 3] = 255; // Alpha channel
  234. // add black border
  235. if(x < 8 || y < 8 || x > (canvasData.width - 8) || y > (canvasData.height - 8))
  236. {
  237. canvasData.data[idx + 0] = 0;
  238. canvasData.data[idx + 1] = 0;
  239. canvasData.data[idx + 2] = 0;
  240. }
  241. }
  242. }
  243. return canvasData;
  244. }
  245. //6.连环画效果
  246. //连环画的效果与图像灰度化后的效果相似,它们都是灰度图,但连环画增大了图像的对比度,使整体明暗效果更强.
  247. //算法:
  248. //R = |g – b + g + r| * r / 256
  249. //G = |b – g + b + r| * r / 256;
  250. //B = |b – g + b + r| * g / 256;
  251. function comic(canvasData)
  252. {
  253. for ( var x = 0; x < canvasData.width; x++) {
  254. for ( var y = 0; y < canvasData.height; y++) {
  255. // Index of the pixel in the array
  256. var idx = (x + y * canvasData.width) * 4;
  257. var r = canvasData.data[idx + 0];
  258. var g = canvasData.data[idx + 1];
  259. var b = canvasData.data[idx + 2];
  260. var fr=Math.abs((g-r+g+b))*r/256;
  261. var fg=Math.abs((b-r+g+b))*r/256;
  262. var fb=Math.abs((b-r+g+b))*g/256;
  263. //var fr=(g-r+g+b)*r/256;
  264. //var fg=(b-r+g+b)*r/256;
  265. //var fb=(b-r+g+b)*g/256;
  266. canvasData.data[idx + 0] = fr; // Red channel
  267. canvasData.data[idx + 1] = fg; // Green channel
  268. canvasData.data[idx + 2] = fb; // Blue channel
  269. canvasData.data[idx + 3] = 255; // Alpha channel
  270. // add black border
  271. if(x < 8 || y < 8 || x > (canvasData.width - 8) || y > (canvasData.height - 8))
  272. {
  273. canvasData.data[idx + 0] = 0;
  274. canvasData.data[idx + 1] = 0;
  275. canvasData.data[idx + 2] = 0;
  276. }
  277. }
  278. }
  279. return canvasData;
  280. }
  281. //9 扩散(毛玻璃)
  282. //原理:用当前点四周一定范围内任意一点的颜色来替代当前点颜色,最常用的是随机的采用相邻点进行替代。
  283. function spread(canvasData)
  284. {
  285. for ( var x = 0; x < canvasData.width; x++) {
  286. for ( var y = 0; y < canvasData.height; y++) {
  287. // Index of the pixel in the array
  288. var idx = (x + y * canvasData.width) * 4;
  289. var r = canvasData.data[idx + 0];
  290. var g = canvasData.data[idx + 1];
  291. var b = canvasData.data[idx + 2];
  292. var rand=Math.floor(Math.random()*10)%3;
  293. var idx2 = (x+rand + (y+rand) * canvasData.width) * 4;
  294. var r2 = canvasData.data[idx2 + 0];
  295. var g2 = canvasData.data[idx2 + 1];
  296. var b2 = canvasData.data[idx2 + 2];
  297. var fr=r2;
  298. var fg=g2;
  299. var fb=b2;
  300. canvasData.data[idx + 0] = fr; // Red channel
  301. canvasData.data[idx + 1] = fg; // Green channel
  302. canvasData.data[idx + 2] = fb; // Blue channel
  303. canvasData.data[idx + 3] = 255; // Alpha channel
  304. // add black border
  305. if(x < 8 || y < 8 || x > (canvasData.width - 8) || y > (canvasData.height - 8))
  306. {
  307. canvasData.data[idx + 0] = 0;
  308. canvasData.data[idx + 1] = 0;
  309. canvasData.data[idx + 2] = 0;
  310. }
  311. }
  312. }
  313. return canvasData;
  314. }
  315. var cv = document.getElementById('cv');
  316. var c = cv.getContext('2d');
  317. var canvas = document.getElementById("myCanvas");
  318. var context = canvas.getContext("2d");
  319. var fileBtn = document.getElementById("up-button");
  320. var img = new Image();
  321. fileBtn.onchange = getImg;
  322. function init() {
  323. cv.width = img.width;
  324. cv.height = img.height;
  325. c.drawImage(img, 0, 0);
  326. var f="";
  327. var filter = document.getElementsByName("filter");
  328. for(i=0;i<filter.length;i++)
  329. {
  330. if(filter[i].checked)
  331. {
  332. f=filter[i].id;
  333. }
  334. }
  335. switch(f){
  336. case "gray":setGray();break;
  337. case "spread":setSpread();break;
  338. case "comic":setComic();break;
  339. case "old":setOld();break;
  340. case "negatives":setNegatives();break;
  341. case "black":setBlack();break;
  342. case "cameo":setCameo();break;
  343. case "casting":setCasting();break;
  344. case "frozen":setFrozen();break;
  345. default:setGray();break;
  346. }
  347. };
  348. function getImg(file) {
  349. var reader = new FileReader();
  350. reader.readAsDataURL(fileBtn.files[0]);
  351. reader.onload = function () {
  352. img.src = reader.result;
  353. }
  354. }
  355. window.onload = function() {
  356. img.src = 'http://bbs.blueidea.com/forum.php?mod=attachment&aid=MjEyMzA1fDJiYzQxZThkfDEzODMxMDU2NDd8NjU2ODk5fDMxMDU1MTQ%3D';
  357. img.onload = init
  358. // re-size the canvas deminsion
  359. canvas.width  = img.width;
  360. canvas.height = img.height;
  361. // get 2D render object
  362. var context = canvas.getContext("2d");
  363. context.drawImage(img, 0, 0);
  364. var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);
  365. canvasData=gray(canvasData);
  366. //   canvasData=spread(canvasData);
  367. //   canvasData=old(canvasData);
  368. //   canvasData=frozen(canvasData);
  369. //   canvasData=casting(canvasData);
  370. //   canvasData=cameo(canvasData);
  371. //   canvasData=comic(canvasData);
  372. //   canvasData=black(canvasData);
  373. //   canvasData=negatives(canvasData);
  374. context.putImageData(canvasData, 0, 0); // at coords 0,0
  375. };
  376. document.getElementById('spread').onclick=setSpread;
  377. function setSpread()
  378. {
  379. canvas.width  = img.width;
  380. canvas.height = img.height;
  381. var context = canvas.getContext("2d");
  382. context.drawImage(img, 0, 0);
  383. var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);
  384. canvasData=spread(canvasData);
  385. context.putImageData(canvasData, 0, 0); // at coords 0,0
  386. }
  387. document.getElementById('gray').onclick=setGray;
  388. function setGray()
  389. {
  390. canvas.width  = img.width;
  391. canvas.height = img.height;
  392. var context = canvas.getContext("2d");
  393. context.drawImage(img, 0, 0);
  394. var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);
  395. canvasData=gray(canvasData);
  396. context.putImageData(canvasData, 0, 0); // at coords 0,0
  397. }
  398. document.getElementById('old').onclick=setOld;
  399. function setOld()
  400. {
  401. canvas.width  = img.width;
  402. canvas.height = img.height;
  403. var context = canvas.getContext("2d");
  404. context.drawImage(img, 0, 0);
  405. var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);
  406. canvasData=old(canvasData);
  407. context.putImageData(canvasData, 0, 0); // at coords 0,0
  408. }
  409. document.getElementById('cameo').onclick=setCameo;
  410. function setCameo()
  411. {
  412. canvas.width  = img.width;
  413. canvas.height = img.height;
  414. var context = canvas.getContext("2d");
  415. context.drawImage(img, 0, 0);
  416. var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);
  417. canvasData=cameo(canvasData);
  418. context.putImageData(canvasData, 0, 0); // at coords 0,0
  419. }
  420. document.getElementById('comic').onclick=setComic;
  421. function setComic()
  422. {
  423. canvas.width  = img.width;
  424. canvas.height = img.height;
  425. var context = canvas.getContext("2d");
  426. context.drawImage(img, 0, 0);
  427. var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);
  428. canvasData=comic(canvasData);
  429. context.putImageData(canvasData, 0, 0); // at coords 0,0
  430. }
  431. document.getElementById('black').onclick=setBlack;
  432. function setBlack()
  433. {
  434. canvas.width  = img.width;
  435. canvas.height = img.height;
  436. var context = canvas.getContext("2d");
  437. context.drawImage(img, 0, 0);
  438. var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);
  439. canvasData=black(canvasData);
  440. context.putImageData(canvasData, 0, 0); // at coords 0,0
  441. }
  442. document.getElementById('negatives').onclick=setNegatives;
  443. function setNegatives()
  444. {
  445. canvas.width  = img.width;
  446. canvas.height = img.height;
  447. var context = canvas.getContext("2d");
  448. context.drawImage(img, 0, 0);
  449. var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);
  450. canvasData=negatives(canvasData);
  451. context.putImageData(canvasData, 0, 0); // at coords 0,0
  452. }
  453. </script>
  454. </body>
  455. </html></code>

转《canvas实现滤镜效果》的更多相关文章

  1. 转《在浏览器中使用tensorflow.js进行人脸识别的JavaScript API》

    作者 | Vincent Mühle 编译 | 姗姗 出品 | 人工智能头条(公众号ID:AI_Thinker) [导读]随着深度学习方法的应用,浏览器调用人脸识别技术已经得到了更广泛的应用与提升.在 ...

  2. face-api.js:一个在浏览器中进行人脸识别的 JavaScript 接口

    Mark! 本文将为大家介绍一个建立在「tensorflow.js」内核上的 javascript API——「face-api.js」,它实现了三种卷积神经网络架构,用于完成人脸检测.识别和特征点检 ...

  3. TensorFlow.js之安装与核心概念

    TensorFlow.js是通过WebGL加速.基于浏览器的机器学习js框架.通过tensorflow.js,我们可以在浏览器中开发机器学习.运行现有的模型或者重新训练现有的模型. 一.安装     ...

  4. 在Java中直接调用js代码(转载)

    http://blog.csdn.net/xzyxuanyuan/article/details/8062887 JDK1.6版添加了新的ScriptEngine类,允许用户直接执行js代码. 在Ja ...

  5. 第十一章:WEB浏览器中的javascript

    客户端javascript涵盖在本系列的第二部分第10章,主要讲解javascript是如何在web浏览器中实现的,这些章节介绍了大量的脚本宿主对象,这些对象可以表示浏览器窗口.文档树的内容.这些章节 ...

  6. 在Java中直接调用js代码

    JDK1.6版添加了新的ScriptEngine类,允许用户直接执行js代码. 在Java中直接调用js代码 不能调用浏览器中定义的js函数,会抛出异常提示ReferenceError: “alert ...

  7. TensorFlow.js入门(一)一维向量的学习

    TensorFlow的介绍   TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,其命名来源于本身的运行原理.Tensor(张量)意味着N维数组,Flow(流)意味着 ...

  8. JavaScript权威指南--WEB浏览器中的javascript

    知识要点 1.客户端javascript window对象是所有客户端javascript特性和API的主要接入点.它表示web浏览器的一个窗口或窗体,并且可以用window表示来引用它.window ...

  9. 解决webkit浏览器中js方法中使用window.event提示未定义的问题

    这实际上是一个浏览器兼容性问题,根源百度中一大堆,简要说就是ie中event对象是全局变量,所以哪里都能使用到,但是webkit内核的浏览器中却不存在这个全局变量event,而是以一个隐式的局部变量的 ...

  10. JS Date当前时间:获取日期时间方法在各浏览器中的差异

    转自:http://www.feiesoft.com/00047/<script type="text/javascript"> // JS Date当前时间获取方法在 ...

随机推荐

  1. solidity-library

    library 1)直接使用使用库合约的合约,可以将库合约视为隐式的父合约(base contracts),当然它们不会显式的出现在继承关系中.意思就是不用写is来继承,直接可以在合约中使用: lib ...

  2. MySQL主从同步原理

    mysql主从复制用途 实时灾备,用于故障切换 读写分离,提供查询服务 备份,避免影响业务 主从部署必要条件 主库开启binlo日志(设置log-bin参数) 主从server-id不同 从库可以连同 ...

  3. 浏览器访问svn

    文章转自https://www.cnblogs.com/ayanmw/archive/2011/12/19/2294054.html 你看到的这个文章来自于http://www.cnblogs.com ...

  4. 3990 [模板]矩阵快速幂 洛谷luogu

    题目背景 矩阵快速幂 题目描述 给定n*n的矩阵A,求A^k 输入输出格式 输入格式: 第一行,n,k 第2至n+1行,每行n个数,第i+1行第j个数表示矩阵第i行第j列的元素 输出格式: 输出A^k ...

  5. nodeJS---URL相关模块用法(url和querystring)

    nodeJS---URL相关模块用法(url和querystring) 一: URL模块: URL模块用于解析和处理URL的字符串,提供了如下三个方法: 1. parse 2. format 3. r ...

  6. jenkins安装Scanner插件

    环境centos7 第一步安装scaner插件 第二步 重启之后配置sonarqube 进入Jenkins-->系统管理-->系统设置,找到sonarqube servers,填写相关信息 ...

  7. Sublime Text 3 Build 3143 可用License

    —– BEGIN LICENSE —–TwitterInc200 User LicenseEA7E-8900071D77F72E 390CDD93 4DCBA022 FAF6079061AA12C0 ...

  8. mysql大数据量下的分页

    mysql大数据量使用limit分页,随着页码的增大,查询效率越低下. 测试实验 1.   直接用limit start, count分页语句, 也是我程序中用的方法: select * from p ...

  9. 第三周作业————————word count

    #include <stdio.h> void main() { FILE *fp; , str, word, pu, ch; int g; str = ; word = ; pu = ; ...

  10. javaBean中 字符串 转 date 类型转换

    1-----创建javabean 代码如下 package BeanUtils; import java.util.Date; public class Admin { private String ...