').text(mw.msg('uploadavatar-nofile')); 25 | var hiddenField = $('[name=avatar]'); 26 | var pickfile = $('#pickfile'); 27 | var errorMsg = $('#errorMsg'); 28 | var roundPreview = selector.find('.round-preview'); 29 | 30 | // Helper function to limit the selection clip 31 | function normalizeBound(inner, outer) { 32 | if (inner.left < outer.left) { 33 | inner.left = outer.left; 34 | } 35 | if (inner.left + inner.width > outer.left + outer.width) { 36 | inner.left = outer.left + outer.width - inner.width; 37 | } 38 | if (inner.top < outer.top) { 39 | inner.top = outer.top; 40 | } 41 | if (inner.top + inner.height > outer.top + outer.height) { 42 | inner.top = outer.top + outer.height - inner.height; 43 | } 44 | } 45 | 46 | function normalizeRange(pt, min, max) { 47 | if (pt < min) { 48 | return min; 49 | } else if (pt > max) { 50 | return max; 51 | } else { 52 | return pt; 53 | } 54 | } 55 | 56 | // Helper function to easily get bound 57 | function getBound(obj) { 58 | var bound = obj.offset(); 59 | bound.width = obj.width(); 60 | bound.height = obj.height(); 61 | return bound; 62 | } 63 | 64 | function setBound(obj, bound) { 65 | obj.offset(bound); 66 | obj.width(bound.width); 67 | obj.height(bound.height); 68 | } 69 | 70 | function cropImage(image, x, y, dim, targetDim) { 71 | if (dim > 2 * targetDim) { 72 | var crop = cropImage(image, x, y, dim, 2 * targetDim); 73 | return cropImage(crop, 0, 0, 2 * targetDim, targetDim); 74 | } else { 75 | var buffer = $('') 76 | .attr('width', targetDim) 77 | .attr('height', targetDim)[0]; 78 | buffer 79 | .getContext('2d') 80 | .drawImage(image, x, y, dim, dim, 0, 0, targetDim, targetDim); 81 | return buffer; 82 | } 83 | } 84 | 85 | // Event listeners 86 | function updateHidden() { 87 | var bound = getBound(selector); 88 | var outer = getBound(container); 89 | // When window is zoomed, 90 | // width set != width get, so we do some nasty trick here to counter the effect 91 | var dim = Math.round((bound.width - container.width() + visualWidth) * multiplier); 92 | var res = dim; 93 | if (res > maxRes) { 94 | res = maxRes; 95 | } 96 | var image = cropImage(imageObj[0], 97 | (bound.left - outer.left) * multiplier, 98 | (bound.top - outer.top) * multiplier, 99 | dim, res); 100 | hiddenField.val(image.toDataURL()); 101 | 102 | // We have an image here, so we can easily calcaulte the reverse color 103 | var data = image.getContext('2d').getImageData(0, 0, res, res).data; 104 | var r = 0, g = 0, b = 0, c = 0; 105 | for (var i = 0; i < data.length; i += 4) { 106 | c++; 107 | r += data[i]; 108 | g += data[i + 1]; 109 | b += data[i + 2]; 110 | } 111 | 112 | roundPreview.css('border-color', 'rgb(' + (256 - Math.round(r / c)) + ', ' + (256 - Math.round(g / c)) + ',' + (256 - Math.round(b / c)) + ')'); 113 | } 114 | 115 | function onDragStart(event) { 116 | startOffset = getBound(selector); 117 | startX = event.pageX; 118 | startY = event.pageY; 119 | event.preventDefault(); 120 | event.stopPropagation(); 121 | 122 | $('body').on('mousemove', onDrag).on('mouseup', onDragEnd); 123 | } 124 | 125 | function onDrag(event) { 126 | var bound = getBound(selector); 127 | var outer = getBound(container); 128 | var point = { 129 | left: event.pageX, 130 | top: event.pageY, 131 | width: 0, 132 | height: 0 133 | }; 134 | normalizeBound(point, outer); 135 | var deltaX = point.left - startX; 136 | var deltaY = point.top - startY; 137 | 138 | // All min, max below uses X direction as positive 139 | switch(dragMode) { 140 | case 0: 141 | bound.left = startOffset.left + deltaX; 142 | bound.top = startOffset.top + deltaY; 143 | normalizeBound(bound, outer); 144 | break; 145 | case 1: 146 | var min = -Math.min(startOffset.left - outer.left, startOffset.top - outer.top); 147 | var max = startOffset.width - minDimension; 148 | deltaX = deltaY = normalizeRange(Math.min(deltaX, deltaY), min, max); 149 | bound.width = startOffset.width - deltaX; 150 | bound.left = startOffset.left + startOffset.width - bound.width; 151 | bound.height = startOffset.height - deltaY; 152 | bound.top = startOffset.top + startOffset.height - bound.height; 153 | break; 154 | case 2: 155 | var min = minDimension - startOffset.width; 156 | var max = Math.min( 157 | outer.left + outer.width - startOffset.left - startOffset.width, 158 | startOffset.top - outer.top 159 | ); 160 | deltaY = -(deltaX = normalizeRange(Math.max(deltaX, -deltaY), min, max)); 161 | bound.width = startOffset.width + deltaX; 162 | bound.height = startOffset.height - deltaY; 163 | bound.top = startOffset.top + startOffset.height - bound.height; 164 | break; 165 | case 3: 166 | var min = -Math.min( 167 | startOffset.left - outer.left, 168 | outer.top + outer.height - startOffset.top - startOffset.height 169 | ); 170 | var max = startOffset.width - minDimension; 171 | deltaY = -(deltaX = normalizeRange(Math.min(deltaX, -deltaY), min, max)); 172 | bound.width = startOffset.width - deltaX; 173 | bound.left = startOffset.left + startOffset.width - bound.width; 174 | bound.height = startOffset.height + deltaY; 175 | break; 176 | case 4: 177 | var min = minDimension - startOffset.width; 178 | var max = Math.min( 179 | outer.left + outer.width - startOffset.left - startOffset.width, 180 | outer.top + outer.height - startOffset.top - startOffset.height 181 | ); 182 | deltaX = deltaY = normalizeRange(Math.max(deltaX, deltaY), min, max); 183 | bound.width = startOffset.width + deltaX; 184 | bound.height = startOffset.height + deltaY; 185 | break; 186 | } 187 | 188 | setBound(selector, bound); 189 | event.preventDefault(); 190 | } 191 | 192 | function onDragEnd(event) { 193 | $('body').off('mousemove', onDrag).off('mouseup', onDragEnd); 194 | event.preventDefault(); 195 | 196 | updateHidden(); 197 | } 198 | 199 | function onImageLoaded() { 200 | var width = imageObj.width(); 201 | var height = imageObj.height(); 202 | 203 | if (width < minDimension || height < minDimension) { 204 | errorMsg.text(mw.msg('avatar-toosmall')); 205 | imageObj.attr('src', ''); 206 | container.attr('disabled', ''); 207 | currentAvatar.show(); 208 | msgBelow.text(mw.msg('uploadavatar-nofile')); 209 | submitButton.attr('disabled', ''); 210 | return; 211 | } 212 | 213 | errorMsg.text(''); 214 | 215 | container.removeAttr('disabled'); 216 | submitButton.removeAttr('disabled'); 217 | currentAvatar.hide(); 218 | msgBelow.text(mw.msg('uploadavatar-hint')); 219 | visualHeight = height; 220 | visualWidth = width; 221 | 222 | if (visualHeight > maxVisualHeight) { 223 | visualHeight = maxVisualHeight; 224 | visualWidth = visualHeight * width / height; 225 | } 226 | 227 | multiplier = width / visualWidth; 228 | minVisualDim = minDimension / multiplier; 229 | 230 | container.width(visualWidth); 231 | container.height(visualHeight); 232 | imageObj.width(visualWidth); 233 | imageObj.height(visualHeight); 234 | 235 | var bound = getBound(container); 236 | bound.width = bound.height = Math.min(bound.width, bound.height); 237 | setBound(selector, bound); 238 | updateHidden(); 239 | } 240 | 241 | function onImageLoadingFailed() { 242 | if(!imageObj.attr('src')) { 243 | return; 244 | } 245 | 246 | errorMsg.text(mw.msg('avatar-invalid')); 247 | imageObj.attr('src', ''); 248 | container.attr('disabled', ''); 249 | submitButton.attr('disabled', ''); 250 | currentAvatar.show(); 251 | msgBelow.text(mw.msg('uploadavatar-nofile')); 252 | return; 253 | } 254 | 255 | // Event registration 256 | selector.on('mousedown', function(event) { 257 | dragMode = 0; 258 | onDragStart(event); 259 | }); 260 | selector.find('.tl-resizer').on('mousedown', function(event) { 261 | dragMode = 1; 262 | onDragStart(event); 263 | }); 264 | selector.find('.tr-resizer').on('mousedown', function(event) { 265 | dragMode = 2; 266 | onDragStart(event); 267 | }); 268 | selector.find('.bl-resizer').on('mousedown', function(event) { 269 | dragMode = 3; 270 | onDragStart(event); 271 | }); 272 | selector.find('.br-resizer').on('mousedown', function(event) { 273 | dragMode = 4; 274 | onDragStart(event); 275 | }); 276 | 277 | pickfile.click(function(event) { 278 | var picker = $(''); 279 | picker.change(function(event) { 280 | var file = event.target.files[0]; 281 | if (file) { 282 | var reader = new FileReader(); 283 | reader.onloadend = function() { 284 | imageObj.width('auto').height('auto'); 285 | imageObj.attr('src', reader.result); 286 | } 287 | reader.readAsDataURL(file); 288 | } 289 | }); 290 | picker.click(); 291 | event.preventDefault(); 292 | }); 293 | 294 | imageObj 295 | .on('load', onImageLoaded) 296 | .on('error', onImageLoadingFailed); 297 | 298 | 299 | // UI modification 300 | submitButton.attr('disabled', ''); 301 | container.append(imageObj); 302 | container.append(selector); 303 | hiddenField.before(currentAvatar); 304 | hiddenField.before(container); 305 | hiddenField.before(msgBelow); --------------------------------------------------------------------------------