1 /* 2 * Copyright (C) 2009 Mihai Şucan 3 * 4 * This file is part of PaintWeb. 5 * 6 * PaintWeb is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * PaintWeb is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with PaintWeb. If not, see <http://www.gnu.org/licenses/>. 18 * 19 * $URL: http://code.google.com/p/paintweb $ 20 * $Date: 2009-11-10 20:12:34 +0200 $ 21 */ 22 23 /** 24 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a> 25 * @fileOverview Holds the color bucket tool implementation, also known as the 26 * flood fill tool. 27 */ 28 29 /** 30 * @class The color bucket tool. 31 * 32 * The implementation here is based on the seed fill algorithm of Paul S. 33 * Heckbert (1990). 34 * 35 * @param {PaintWeb} app Reference to the main paint application object. 36 */ 37 pwlib.tools.cbucket = function (app) { 38 var _self = this, 39 config = app.config, 40 layer = app.layer.context, 41 buffer = app.buffer.context, 42 iwidth = app.image.width, 43 iheight = app.image.height, 44 mouse = app.mouse; 45 46 var stackMax = 10000; // maximum depth of stack 47 var lines = []; // stack of lines 48 var pixelNew, layerpix; 49 50 /** 51 * The <code>preActivate</code> event handler. This method checks if the 52 * browser implements the <code>getImageData()</code> and 53 * <code>putImageData()</code> context methods. If not, the color bucket tool 54 * cannot be used. 55 * 56 * @returns {Boolean} True if the drawing tool can be activated, or false 57 * otherwise. 58 */ 59 this.preActivate = function () { 60 // The latest versions of all browsers which implement Canvas, also 61 // implement the getImageData() method. This was only a problem with some 62 // old versions (eg. Opera 9.2). 63 if (!layer.getImageData || !layer.putImageData) { 64 alert(app.lang.errorCbucketUnsupported); 65 return false; 66 } else { 67 return true; 68 } 69 }; 70 71 /** 72 * The <code>activate</code> event handler. Canvas shadow rendering is 73 * disabled. 74 */ 75 this.activate = function () { 76 app.shadowDisallow(); 77 }; 78 79 /** 80 * The <code>deactivate</code> event handler. Canvas shadow rendering is 81 * allowed once again. 82 */ 83 this.deactivate = function () { 84 app.shadowAllow(); 85 }; 86 87 /** 88 * The <code>click</code> and <code>contextmenu</code> event handler. This 89 * method performs the flood fill operation. 90 * 91 * @param {Event} ev The DOM Event object. 92 * 93 * @returns {Boolean} True if the image was modified, or false otherwise. 94 */ 95 this.click = function (ev) { 96 // Allow the user to right-click or hold down the Shift key to use the 97 // border color for filling the image. 98 if (ev.type === 'contextmenu' || ev.button === 2 || ev.shiftKey) { 99 var fillStyle = buffer.fillStyle; 100 buffer.fillStyle = buffer.strokeStyle; 101 buffer.fillRect(0, 0, 1, 1); 102 buffer.fillStyle = fillStyle; 103 } else { 104 buffer.fillRect(0, 0, 1, 1); 105 } 106 107 // Instead of parsing the fillStyle ... 108 pixelNew = buffer.getImageData(0, 0, 1, 1); 109 pixelNew = [pixelNew.data[0], pixelNew.data[1], pixelNew.data[2], 110 pixelNew.data[3]]; 111 112 buffer.clearRect(0, 0, 1, 1); 113 114 var pixelOld = layer.getImageData(mouse.x, mouse.y, 1, 1).data; 115 pixelOld = pixelOld[0] + ';' + pixelOld[1] + ';' + pixelOld[2] + ';' 116 + pixelOld[3]; 117 118 if (pixelOld === pixelNew.join(';')) { 119 return false; 120 } 121 122 fill(mouse.x, mouse.y, pixelOld); 123 124 app.historyAdd(); 125 126 return true; 127 }; 128 this.contextmenu = this.click; 129 130 /** 131 * Fill the image with the current fill color, starting from the <var>x</var> 132 * and <var>y</var> coordinates. 133 * 134 * @private 135 * 136 * @param {Number} x The x coordinate for the starting point. 137 * @param {Number} y The y coordinate for the starting point. 138 * @param {String} pixelOld The old pixel value. 139 */ 140 var fill = function (x, y, pixelOld) { 141 var start, x1, x2, dy, tmp, idata; 142 143 pushLine(y, x, x, 1); // needed in some cases 144 pushLine(y + 1, x, x, -1); // seed segment (popped 1st) 145 146 while (lines.length > 0) { 147 // pop segment off stack and fill a neighboring scan line 148 tmp = lines.pop(); 149 dy = tmp[3]; 150 y = tmp[0] + dy; 151 x1 = tmp[1]; 152 x2 = tmp[2]; 153 154 layerpix = null; 155 idata = layer.getImageData(0, y, iwidth, 1); 156 layerpix = idata.data; 157 158 // segment of scan line y-dy for x1 <= x <= x2 was previously filled, now 159 // explore adjacent pixels in scan line y 160 for (x = x1; x >= 0 && pixelRead(x) === pixelOld; x--) { 161 pixelWrite(x); 162 } 163 164 if (x >= x1) { 165 for (x++; x <= x2 && pixelRead(x) !== pixelOld; x++); 166 start = x; 167 if (x > x2) { 168 layer.putImageData(idata, 0, y); 169 continue; 170 } 171 172 } else { 173 start = x + 1; 174 if (start < x1) { 175 pushLine(y, start, x1 - 1, -dy); // leak on left? 176 } 177 178 x = x1 + 1; 179 } 180 181 do { 182 for (; x < iwidth && pixelRead(x) === pixelOld; x++) { 183 pixelWrite(x); 184 } 185 186 pushLine(y, start, x - 1, dy); 187 if (x > (x2 + 1)) { 188 pushLine(y, x2 + 1, x - 1, -dy); // leak on right? 189 } 190 191 for (x++; x <= x2 && pixelRead(x) !== pixelOld; x++); 192 start = x; 193 194 } while (x <= x2); 195 196 layer.putImageData(idata, 0, y); 197 } 198 199 layerpix = null; 200 idata = null; 201 }; 202 203 var pushLine = function (y, xl, xr, dy) { 204 if (lines.length < stackMax && (y+dy) >= 0 && (y+dy) < iheight) { 205 lines.push([y, xl, xr, dy]); 206 } 207 }; 208 209 var pixelRead = function (x) { 210 var r = 4 * x; 211 return layerpix[r] + ';' + layerpix[r+1] + ';' + layerpix[r+2] + ';' 212 + layerpix[r+3]; 213 }; 214 215 var pixelWrite = function (x) { 216 var r = 4 * x; 217 layerpix[r] = pixelNew[0]; 218 layerpix[r+1] = pixelNew[1]; 219 layerpix[r+2] = pixelNew[2]; 220 layerpix[r+3] = pixelNew[3]; 221 }; 222 }; 223 224 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: 225 226