});
image.src = url;
}
Нам надо поменять размер холста, чтобы он соответствовал картинке. Почему-то при смене размера холста его контекст забывает все настройки (fillStyle
и lineWidth
), в связи с чем функция сохраняет их и загружает обратно после обновления размера холста.
Элемент управления для загрузки локального файла использует технику FileReader
из главы 18. Кроме используемого здесь метода readAsText
у таких объектов есть метод под названием readAsDataURL
– а это то, что нам нужно. Мы загружаем файл, который пользователь выбирает, как URL с данными, и передаём его в loadImageURL
для вывода на холст.
controls.openFile = function(cx) {
var input = elt("input", {type: "file"});
input.addEventListener("change", function() {
if (input.files.length == 0) return;
var reader = new FileReader();
reader.addEventListener("load", function() {
loadImageURL(cx, reader.result);
});
reader.readAsDataURL(input.files[0]);
});
return elt("div", null, "Open file: ", input);
};
Загружать файл с URL ещё проще. Но с текстовым полем мы не знаем, закончил ли пользователь набирать в нём URL, поэтому мы не можем просто слушать события “change”
. Вместо этого мы обернём поле в форму и среагируем, когда она будет отправлена – либо по нажатии Enter, либо по нажатии кнопки load.
controls.openURL = function(cx) {
var input = elt("input", {type: "text"});
var form = elt("form", null,
"Open URL: ", input,
elt("button", {type: "submit"}, "load"));
form.addEventListener("submit", function(event) {
event.preventDefault();
loadImageURL(cx, form.querySelector("input").value);
});
return form;
};
Теперь мы определили все элементы управления, требующиеся нашей программе, но нужно добавить ещё несколько инструментов.
Закругляемся
Очень просто можно добавить инструмент для вывода текста, который выводит запрос пользователю, куда он должен ввести текст.
tools.Text = function(event, cx) {
var text = prompt("Text:", "");
if (text) {
var pos = relativePos(event, cx.canvas);
cx.font = Math.max(7, cx.lineWidth) + "px sans-serif";
cx.fillText(text, pos.x, pos.y);
}
};
Можно было бы добавить полей для размера текста и шрифта, но для простоты мы всегда используем шрифт sans-serif и размер шрифта, как у текущей кисти. Минимальный размер – 7 пикселей, потому что меньше текст будет нечитаемый.
Ещё один необходимый инструмент для каляк-маляк – “аэрозоль”. Она рисует случайные точки под кистью, пока нажата кнопка мыши, создавая более или менее густые точки в зависимости от скорости движения курсора.
tools.Spray = function(event, cx) {
var radius = cx.lineWidth / 2;
var area = radius * radius * Math.PI;
var dotsPerTick = Math.ceil(area / 30);
var currentPos = relativePos(event, cx.canvas);
var spray = setInterval(function() {
for (var i = 0; i < dotsPerTick; i++) {
var offset = randomPointInRadius(radius);
cx.fillRect(currentPos.x + offset.x,
currentPos.y + offset.y, 1, 1);
}
}, 25);
trackDrag(function(event) {
currentPos = relativePos(event, cx.canvas);
}, function() {
clearInterval(spray);
});
};
Аэрозоль использует setInterval
для выплёвывания цветных точек каждые 25 миллисекунд, пока нажата кнопка мыши. Функция trackDrag
используется для того, чтобы currentPos
указывала на текущее положение курсора, и для выключения интервала при отпускании кнопки.
Чтобы посчитать, сколько точек нужно нарисовать каждый раз по окончанию интервала, функция подсчитывает размер области текущей кисти и делит его на 30. Для поиска случайного положения под кистью используется функция randomPointInRadius
.
function randomPointInRadius(radius) {
for (;;) {
var x = Math.random() * 2 - 1;
var y = Math.random() * 2 - 1;
if (x * x + y * y <= 1)
return {x: x * radius, y: y * radius};
}
}
Эта функция создаёт точки в квадрате между (-1,-1) и (1,1). Используя теорему Пифагора, она проверяет, лежит ли созданная точка внутри круга с радиусом 1. Когда такая точка находится, она возвращает её координаты, умноженные на радиус.
Цикл нужен для равномерного распределения точек. Проще было бы создавать точки в круге, взяв случайный угол и радиус и вызвав Math.sin
и Math.cos
для создания точки. Но тогда точки с большей вероятностью появлялись бы ближе к центру круга. Это ограничение можно обойти, но результат будет сложнее, чем предыдущий цикл.