Статьи

Создание редактора изображений Canvas с помощью Canvas

Мы не собираемся сходить с ума от этого, просто не будет времени, но мы увидим, как легко делать такие вещи, как вращение, изменение размера, перевод и даже тонкие манипуляции с цветом. Не обманывайте себя, что мы закончим с равным Photoshop, хотя это теоретически возможно, но, учитывая, что мы работаем в рамках ничего более сложного, чем браузер, лично я все же думаю, что это довольно замечательно.

Этот урок включает в себя скринкаст, доступный для участников Tuts + Premium.


Чтобы создать рабочую версию демо-версии локально, вам нужно использовать браузер на основе Webkit, такой как Safari, Chrome или Opera. Демонстрация будет работать в Firefox, но для работы большинства функций ее нужно будет запустить через веб-сервер. Даже не думайте об использовании IE; только версия 9 даже поддерживает поддержку элемента canvas, и, честно говоря, я бы даже не поверил в IE9 для правильной визуализации кода и функциональности.


Базовый HTML действительно довольно тривиален; Все, что нам нужно для структуры редактора, это следующие основные элементы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang=»en»>
    <head>
        <meta charset=»utf-8″>
        <title>Canvas Image Editor</title>
        <link rel=»stylesheet» href=»image-editor.css»>
        <link rel=»stylesheet» href=»ui-lightness/jquery-ui-1.8.7.custom.css»>
    </head>
    <body>
        <div id=»imageEditor»>
            <section id=»editorContainer»>
                <canvas id=»editor» width=»480″ height=»480″></canvas>
            </section>
            <section id=»toolbar»>
                <a id=»save» href=»#» title=»Save»>Save</a>
                <a id=»rotateL» href=»#» title=»Rotate left»>Rotate Left</a>
                <a id=»rotateR» href=»#» title=»Rotate right»>Rotate Right</a>
                <a id=»resize» href=»#» title=»Resize»>Resize</a>
                <a id=»greyscale» href=»#» title=»Convert to grayscale»>B&W</a>
                <a id=»sepia» href=»#» title=»Convert to sepia tone»>Sepia</a>
            </section>
        </div>
        <script src=»jquery-1.4.4.min.js»></script>
        <script src=»jquery-ui-1.8.7.custom.min.js»></script>
        <script>
        </script>
    </body>
</html>

Сохраните страницу как image-editor.html . Помимо стандартных HTML-элементов, составляющих каркас страницы, у нас есть настраиваемая таблица стилей, которую мы добавим буквально через мгновение, и таблица стилей, предоставляемая пользовательским интерфейсом jQuery. В нижней части страницы, перед закрывающим тегом </ body> , есть ссылка на jQuery (текущая версия на момент написания статьи 1.4.4), ссылка на пользовательский интерфейс jQuery (текущая версия 1.8.7 ) и пустой тег сценария, в который мы поместим код, который дает редактору его функциональность.

Компоненты jQuery UI, которые мы будем использовать в этом примере, имеют изменяемый размер и диалоговое окно, а тема — ui-lightness.

Видимые элементы на странице довольно простые; у нас есть внешний содержащий элемент <div> , внутри которого находятся два элемента <section> . Первый содержит элемент <canvas>, который мы будем использовать для управления нашим изображением. Второй содержит панель инструментов с кнопками, которые будут использоваться для выполнения манипуляций. Из атрибутов id, присвоенных каждой кнопке, должно быть достаточно очевидно, что делает каждая кнопка.


Как и HTML, используемый CSS чрезвычайно прост и состоит из следующего:

1
2
3
4
5
6
7
#imageEditor { width:482px;
#editorContainer { display:block;
#editor { display:block;
#toolbar { display:block;
#toolbar a { margin-right:10px;
#resizer { border:2px dashed #000;
#tip { padding:5px;

Сохраните это как image-editor.css в том же каталоге, что и HTML-страница. Здесь нет ничего по-настоящему примечательного, в основном стили размещают редактор и его составляющие элементы, как показано на скриншоте ниже:




Осталось только добавить код, который заставит редактор работать. Начните с добавления приведенного ниже кода в пустой элемент <script> внизу страницы:

01
02
03
04
05
06
07
08
09
10
11
12
13
(function($){
    //get canvas and context
    var editor = document.getElementById(«editor»),
        context = editor.getContext(«2d»),
        //create/load image
        image = $(«<img/>», {
            src: «img/graffiti.png»,
            load: function() {
                context.drawImage(this, 0, 0);
            }
    }),
    //more code to follow here…
})(jQuery);

Прежде всего, мы помещаем весь наш код в замыкание и накладываем псевдоним на объект jQuery как символ $ . Я понимаю, что jQuery автоматически присваивает нам псевдоним $, однако в некоторых ситуациях это может привести к конфликтам с другим кодом, который может использоваться, когда наш код реализуется другими.

Это также рекомендуемый способ создания плагинов, поэтому простое выполнение этого каждый раз, когда используется jQuery, позволяет намного быстрее и проще возвращаться и превращать код в плагин, если это необходимо. Я просто нахожу более удобным написать весь мой код jQuery в таком замыкании. У него есть и другие преимущества, такие как удаление зависимости от глобальных переменных — все создаваемые нами переменные будут надежно скрыты от другого кода за пределами замыкания в большинстве ситуаций.

Для работы с холстом нам нужно получить ссылку на его контекст. Это объект, представляющий поверхность холста, на которой вызывается большинство методов работы с холстом. Несколько методов могут быть вызваны напрямую для объекта canvas, но большинство из них вызывается для объекта контекста.

Получение контекста — двухэтапный процесс; сначала мы получаем ссылку на сам элемент <canvas> , используя стандартный метод JavaScript document.getElementById () , затем вызываем метод getContext () на холсте (это один из таких методов, который можно напрямую вызывать для элемента canvas, и не зря!), указав 2d в качестве контекста. В настоящее время 2d является единственным широко распространенным контекстом, хотя в будущем мы можем рассчитывать на работу с 3D-контекстами.

Оба объекта: canvas и context хранятся в переменных верхнего уровня (эквивалент глобальных переменных, в которых мы не работали в замыкании), так что любые функции, которые мы определяем, могут использовать их.

После этих двух переменных мы создаем другую, называемую image . Мы используем jQuery здесь, чтобы быстро и без усилий создать новый элемент изображения. Мы устанавливаем src для изображения (образец, изображение без лицензионных отчислений включено в загрузку кода), чтобы у нас было с чем работать, и добавляем обработчик события onload, который просто рисует изображение на холсте, загруженном изображением , При работе с холстом важно убедиться, что все используемые изображения полностью загружены перед их добавлением на холст.

Изображение рисуется на холсте с помощью метода drawImage () , который вызывается для объекта контекста . Этот метод принимает три аргумента (он может дополнительно принимать больше, как мы увидим позже в примере); Эти аргументы — это используемое изображение (называемое ключевым словом this ), позиция x на холсте, чтобы начать рисование изображения, и позиция y на холсте, чтобы начать рисование изображения. Образец изображения — точный размер холста в этом примере, и для простоты он полностью квадратный.

Теперь мы готовы добавить еще немного кода на этом этапе. После созданного нами изображения есть запятая, поэтому следующий фрагмент кода, который мы добавим, также будет переменной.


Далее нам нужно добавить некоторый код для обработки кнопок панели инструментов. Мы могли бы просто добавить серию обработчиков кликов, по одному для каждой кнопки, которую мы хотим добавить. Хотя это легко сделать, оно не будет чрезвычайно эффективным и не будет невероятно хорошо масштабироваться.

В идеале нам нужна одна основная функция, которая обрабатывает нажатие на любую кнопку и вызывает правильную функцию. К счастью, это тоже очень просто. Добавьте следующий код сразу после только что созданного изображения:

1
2
3
4
5
6
7
8
//toolbar functions
tools = {
};
$(«#toolbar»).children().click(function(e) {
    e.preventDefault();
    //call the relevant function
    tools[this.id].call(this);
});

Это так просто. Давайте рассмотрим, что делает этот код. Последняя переменная верхнего уровня, которую мы создаем, называется tools и содержит пустой (на данном этапе) объект. Все функции для каждой отдельной кнопки будут помещены в этот объект, как мы вскоре увидим.

Затем мы добавляем обработчик одного клика , прикрепленный ко всем дочерним элементам панели инструментов id (один из элементов <section> в базовом HTML).

В рамках этой функции обработки кликов мы сначала останавливаем поведение браузера по умолчанию, которое заключается в переходе по ссылке (это предотвращает нежелательное поведение «прыжок наверх», которое иногда проявляется при использовании в качестве кнопок стандартных элементов <a> ).

Наконец, мы применяем метод JavaScript call () к функции, содержащейся в свойстве объекта tools, который соответствует атрибуту id ссылки, по которой щелкнули. Поскольку по ссылке, по которой щелкнули, все, что нам нужно, это атрибут id , мы можем использовать стандартное ключевое слово this, не заключая его в объект jQuery. Метод call () требует, чтобы ключевое слово this также передавалось ему в качестве аргумента.

Теперь, если мы добавим функцию к объекту инструментов под свойством save , всякий раз, когда мы нажимаем кнопку панели инструментов с сохранением идентификатора, вызывается функция. Итак, все, что нам нужно сделать сейчас, это добавить функцию для каждой из кнопок панели инструментов.


Мы начнем с функции сохранения, поскольку она довольно маленькая и простая. Добавьте следующий код в объект tools :

01
02
03
04
05
06
07
08
09
10
11
12
13
//output to <img>
save: function() {
    var saveDialog = $(«<div>»).appendTo(«body»);
    $(«<img/>», {
        src: editor.toDataURL()
    }).appendTo(saveDialog);
    saveDialog.dialog({
        resizable: false,
        modal: true,
        title: «Right-click and choose ‘Save Image As'»,
        width: editor.width + 35
    });
},

Первое, что делает наша функция, это создает новый элемент <div> и добавляет его к <body> страницы. Этот элемент будет использоваться вместе с компонентом диалога пользовательского интерфейса jQuery для создания модального диалогового окна.

Далее мы создаем новый элемент изображения; на этот раз мы установили в src представление в кодировке base-64 всего, что содержится в канве. Это действие выполняется с помощью метода toDataURL () , который, кстати, является еще одним методом, вызываемым непосредственно в элементе canvas. После создания изображение добавляется к элементу <div>, созданному на предыдущем шаге.

Наконец, мы инициализируем компонент диалога; это делается с помощью метода dialog (), метода, добавленного пользовательским интерфейсом jQuery. Литерал объекта передается методу dialog (), который позволяет нам устанавливать его различные параметры конфигурации. В этом случае мы настраиваем диалоговое окно таким образом, чтобы оно не изменяло размеры и чтобы оно было модальным (наложение будет применено к остальной части страницы, пока диалоговое окно открыто). Мы также устанавливаем заголовок диалогового окна в строку, дающую инструкции о том, что делать, чтобы сохранить объект, и делаем диалог достаточно большим, чтобы содержать изображение, устанавливая его ширину в ширину элемента canvas плюс 35 пикселей.


Общей особенностью редакторов изображений является возможность вращать элемент, и с помощью встроенной функции холста это довольно легко реализовать в нашем редакторе. Добавьте следующие функции после функции сохранения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
rotate: function(conf) {
    //save current image before rotating
    $(«<img/>», {
        src: editor.toDataURL(),
        load: function() {
            //rotate canvas
            context.clearRect(0, 0, editor.width, editor.height);
            context.translate(conf.x, conf.y);
            context.rotate(conf.r);
            //redraw saved image
            context.drawImage(this, 0, 0);
        }
    });
},
rotateL: function() {
    var conf = {
        x: 0,
        y: editor.height,
        r: -90 * Math.PI / 180
    };
    tools.rotate(conf);
},
rotateR: function() {
    var conf = {
        x: editor.width,
        y: 0,
        r: 90 * Math.PI / 180
    };
    tools.rotate(conf);
},

Итак, мы добавили три новые функции; мастер-функция поворота, а затем — каждая функция для кнопок панели поворота влево и поворота вправо. Функции rotateL () и rotateR () просто создают пользовательский объект конфигурации и затем вызывают основную функцию rotate () , передавая объект конфигурации. Это главная функция rotate (), которая фактически выполняет вращение. Важно отметить, что вращается сам холст, а не изображение на холсте.

Конфигурационный объект, который мы создаем в функциях rotateL () и rotateR (), очень прост; он содержит три свойства — x , y и r . Свойства x и y относятся к необходимому количеству перевода, а r — к количеству вращения. Каждый щелчок на кнопке поворота поворачивает изображение на плюс или минус 90 градусов.

В функции rotateL () нам нужно перевести холст на то же расстояние, что и высота холста по вертикальной оси, чтобы изображение оставалось видимым после поворота холста. Вращение влево классифицируется как отрицательное вращение против часовой стрелки. При расчете угла поворота мы не можем использовать градусы, нам нужно преобразовать 90 градусов (или -90 градусов в данном случае) в радианы. Это легко и требует следующей формулы:

Количество градусов, умноженное на Пи, деленное на 180

Функция rotateR () такая же прямолинейная, на этот раз мы переводим полотно на горизонтальную ось вместо вертикальной оси и используем положительное число для вращения по часовой стрелке.

В главной функции rotate () нам снова нужно получить копию текущего изображения на холсте, что мы делаем, создав новый элемент изображения с помощью jQuery и снова установив для его src результат метода toDataURL () . На этот раз мы хотим нарисовать изображение обратно на холст после поворота холста, поэтому мы добавляем обработчик события onload к изображению.

После загрузки изображения мы сначала очищаем холст, используя метод clearRect () . Этот метод принимает четыре аргумента; первые два аргумента — координаты x и y, чтобы начать очистку. Тридцать и четвертый размер области, чтобы очистить. Мы хотим очистить весь холст, поэтому мы начинаем с 0,0 (верхний левый угол холста) и очищаем всю ширину и высоту холста.

Как только мы очистили холст, мы перевели его (по существу, переместили), используя метод преобразования translate () . Мы переведем его либо на полную высоту, либо на полную ширину холста в зависимости от того, инициировала ли функция rotateL () или rotateR () вращение. Перевод холста необходим, потому что по умолчанию вращение происходит вокруг нижней правой части холста, а не в середине, как вы ожидаете.

Затем мы можем повернуть холст, а затем перерисовать изображение обратно на холст. Даже если мы рисуем одно и то же изображение обратно на холст, холст был повернут, поэтому изображение будет автоматически вращаться. Опять же, мы можем сослаться на изображение для передачи в метод drawImage (), как это, потому что мы находимся внутри обработчика загрузки для изображения, а jQuery гарантирует, что это относится к изображению. На следующем скриншоте показано изображение, повернутое влево один раз:



Изменение размера изображения — наше самое сложное взаимодействие, и функция, необходимая для этого, довольно велика, хотя изменение размера самого холста довольно тривиально. Добавьте следующую функцию к объекту tools после функций поворота, которые мы только что рассмотрели:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
resize: function() {
    //create resizable over canvas
    var coords = $(editor).offset(),
        resizer = $(«<div>», {
            id: «resizer»
        }).css({
            position: «absolute»,
            left: coords.left,
            top: coords.top,
            width: editor.width — 1,
            height: editor.height — 1
        }).appendTo(«body»);
  
        var resizeWidth = null,
            resizeHeight = null,
            xpos = editor.offsetLeft + 5,
            ypos = editor.offsetTop + 5;
              
        resizer.resizable({
            aspectRatio: true,
            maxWidth: editor.width — 1,
            maxHeight: editor.height — 1,
              
            resize: function(e, ui) {
                resizeWidth = Math.round(ui.size.width);
                resizeHeight = Math.round(ui.size.height);
              
                //tooltip to show new size
                var string = «New width: » + resizeWidth + «px,<br />new height: » + resizeHeight + «px»;
                  
                if ($(«#tip»).length) {
                    $(«#tip»).html(string);
                } else {
                    var tip = $(«<p></p>», {
                        id: «tip»,
                        html: string
                    }).css({
                        left: xpos,
                        top: ypos
                    }).appendTo(«body»);
                }
            },
            stop: function(e, ui) {
              
                //confirm resize, then do it
                var confirmDialog = $(«<div></div>», {
                    html: «Image will be resized to » + resizeWidth + «px wide, and » + resizeHeight + «px high.<br />Proceed?»
                });
                                  
                //init confirm dialog
                confirmDialog.dialog({
                    resizable: false,
                    modal: true,
                    title: «Confirm resize?»,
                    buttons: {
                        Cancel: function() {
                          
                            //tidy up
                            $(this).dialog(«close»);
                            resizer.remove();
                            $(«#tip»).remove();
                        },
                    Yes: function() {
                      
                        //tidy up
                        $(this).dialog(«close»);
                        resizer.remove();
                        $(«#tip»).remove();
                          
                        $(«<img/>», {
                            src: editor.toDataURL(),
                            load: function() {
  
                                //remove old image
                                context.clearRect(0, 0, editor.width, editor.height);
                                  
                                //resize canvas
                                editor.width = resizeWidth;
                                editor.height = resizeHeight;
                                  
                                //redraw saved image
                                context.drawImage(this, 0, 0, resizeWidth, resizeHeight);
                            }
                        });
                    }
                }
            });
        }
    });
},

Итак, давайте посмотрим шаги, требуемые нашей функцией изменения размера. Сначала нам нужен какой-то способ указать пользователю, к какому размеру будет изменен размер изображения, а пользователю нужен какой-то способ установки нового размера. Затем мы хотим подтвердить, хочет ли пользователь применить новый размер, а затем, если это так, применить новый размер. Новая функция делает все, давайте посмотрим как.

Сначала мы получаем координаты на странице элемента canvas, используя метод jQuery offset () , чтобы мы знали, где расположить изменяемый размер элемента. Затем мы создаем новый элемент <div> , который станет изменяемого размера , и присваиваем ему идентификатор изменения размера, чтобы мы могли легко обращаться к нему. Мы также установили некоторые свойства стиля нового элемента, чтобы расположить его поверх холста. Как только эти стили установлены, мы добавляем их в <body> страницы. Изменяемый размер будет отображаться в виде пунктирной границы внутри холста, как показано ниже:


Затем мы инициализируем еще две переменные, но пока устанавливаем их значения равными нулю . Они будут использоваться для хранения ширины и высоты, на которые изменяется изменяемый размер при изменении размера. Эти переменные будут заполнены и использованы позже в функции. Переменные xpos и ypos используются для позиционирования всплывающей подсказки, которую мы создадим через минуту. Они располагают всплывающую подсказку на 5 пикселей от левого края и верхнего края холста.

Затем мы инициализируем изменение размера с помощью метода resizable () пользовательского интерфейса jQuery. Мы настраиваем изменяемый размер, чтобы его соотношение сторон было заблокировано; наше изображение квадратное, поэтому мы хотим, чтобы оно оставалось квадратным независимо от его размера. Мы также гарантируем, что изображение не может быть увеличено в размере, чтобы качество изображения сохранялось. Если изображение будет увеличено, а не уменьшено, оно станет блочным. Параметры конфигурации maxWidth и maxHeight гарантируют, что изображение можно будет только уменьшить. Оба установлены на текущую ширину и высоту холста соответственно.

Компонент с изменяемым размером имеет несколько пользовательских обработчиков событий, которые мы можем назначить функциям, которые будут выполняться при запуске этих пользовательских событий. Мы используем два из этих обработчиков событий для этого примера; resize , который будет запускаться каждый раз при изменении размера изменяемого размера, и stop, который срабатывает после завершения изменения размера изменяемого размера.

Функция resize может получить два аргумента; первый — это объект события, который нам не нужно использовать в этом примере, но который должен быть объявлен, чтобы использовать второй аргумент, который нам нужен. Второй аргумент — это объект, который содержит полезную информацию о изменяемом размере, включая размер, на который он был изменен. Первое, что мы делаем в этой функции, это назначаем новый размер изменяемого размера нашим переменным resizeWidth и resizeHeight, используя свойства объекта пользовательского интерфейса, который получает функция.

Последнее, что делает эта функция, это создает всплывающую подсказку, которая сообщает пользователю, насколько велик размер изменяемого размера в настоящее время. Если эта всплывающая подсказка уже существует, нам не нужно создавать ее заново, и мы можем просто установить ее внутренний текст в строку, показывающую текущий размер изменяемого размера. Если всплывающая подсказка еще не существует, например, при первом изменении размера изменяемого размера, мы создаем его с нуля. Подсказка позиционируется с использованием xpos и ypos, которые мы создали ранее. Строка создается с нуля каждый раз, когда изменяемый размер меняет размер. Подсказка будет выглядеть так:


Функция stop, которая выполняется один раз, когда заканчивается взаимодействие по изменению размера, сначала создает новый элемент диалога, который проверяет, хочет ли посетитель применить новый размер к холсту. Еще раз диалоговое окно настроено так, чтобы оно не изменяло размеры самого себя и чтобы оно было модальным. Мы также добавили несколько кнопок в этот диалог. Первая — кнопка отмены , которая позволяет посетителю отказаться от операции изменения размера без применения нового размера. Все, что мы здесь делаем, — это ведение домашнего хозяйства, удаление диалога, изменение размера и всплывающая подсказка.

Мы также создаем кнопку Да , которая применяет новый размер. Мы также выполняем те же домашние задачи, но затем, чтобы применить новый размер, мы создаем новый образ еще раз, так же, как мы делали это уже несколько раз. Установить новый размер холста очень просто; мы просто предоставляем значения, для которых изменяемый размер был изменен на свойства width и height элемента canvas. Вот диалог, который выглядит так же, как диалог сохранения из ранее, только с другим содержанием:



Ранее я говорил, что у нас есть полный контроль над содержимым элемента canvas на уровне пикселей, так что наши последние две функции кнопок панели инструментов сделают именно это. Первая функция используется для преобразования изображения в оттенки серого, вторая меняет изображение на оттенки сепии. Сначала мы рассмотрим функцию оттенков серого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
greyscale: function() {
    //get image data
    var imgData = context.getImageData(0, 0, editor.width, editor.height),
        pxData = imgData.data,
        length = pxData.length;
    for(var x = 0; x < length; x+=4) {
        //convert to grayscale
        var r = pxData[x],
            g = pxData[x + 1],
            b = pxData[x + 2],
            grey = r * .3 + g * .59 + b * .11;
        pxData[x] = grey;
        pxData[x + 1] = grey;
        pxData[x + 2] = grey;
    }
    //paint grayscale image back
    context.putImageData(imgData, 0, 0);
},

Первое, что нужно сделать этой функции — это получить текущие данные пикселей холста. Для этого мы используем метод getImageData () . Этот метод принимает четыре аргумента, которые совпадают с методом clearRect (), который мы рассматривали ранее — первые два аргумента — это позиции x и y, с которых начинается сбор данных, а третий и четвертый — ширина и высота области. получить. Нам нужен весь холст, поэтому мы начинаем с 0,0 (слева вверху) и продолжаем для ширины и высоты холста. Результирующий объект, возвращаемый методом, сохраняется в переменной imgData .

Объект, хранящийся в этой переменной, имеет свойство, называемое данными ; в этом свойстве находится массив, содержащий значения r g b и alpha для каждого отдельного пикселя на холсте, поэтому первый элемент в массиве будет содержать значение r первого пикселя, второй элемент содержит значение g первого пикселя третий содержит значение b первого пикселя, а четвертый элемент содержит альфа-значение первого пикселя. Этот массив невероятно большой; изображение размером 480 на 480 пикселей содержит 230400 пикселей, и у нас есть четыре элемента для каждого отдельного пикселя. Это делает массив в общей сложности 921600 элементов в длину! Эта длина также сохраняется для использования в цикле for, который мы определим следующим.

Цикл for немного отличается от обычного для циклов. Помните, что массив может быть организован в дискретные блоки из 4 элементов, где каждый элемент в одном блоке относится к отдельным компонентам rgba, поэтому мы перебираем массив по четыре элемента за раз. На каждой итерации мы получаем каждый пиксельный компонент, а затем используем формулу r * .3 + g * .59 + b * .11 для преобразования каждого в оттенки серого. Затем мы сохраняем преобразованный пиксельный компонент обратно в исходный элемент массива.

После того, как мы зациклились по всему массиву, мы можем записать содержимое массива обратно на холст, заменив каждый оригинальный пиксель его новым аналогом в оттенках серого, используя метод putImageData () , который просто делает противоположность getImageData () .

Функция тона сепии идентична функции оттенков серого, за исключением того, что мы используем другую формулу для преобразования каждого компонента r g b в тон сепии:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
sepia: function() {
   //get image data
   var imgData = context.getImageData(0, 0, editor.width, editor.height),
       pxData = imgData.data,
       length = pxData.length;
       for(var x = 0; x < length; x+=4) {
           //convert to grayscale
           var r = pxData[x],
               g = pxData[x + 1],
               b = pxData[x + 2],
           sepiaR = r * .393 + g * .769 + b * .189,
           sepiaG = r * .349 + g * .686 + b * .168,
           sepiaB = r * .272 + g * .534 + b * .131;
           pxData[x] = sepiaR;
           pxData[x + 1] = sepiaG;
           pxData[x + 2] = sepiaB;
       }
                     
       //paint sepia image back
       context.putImageData(imgData, 0, 0);
   }

Вот снимок изображения после его преобразования в сепию:



В этом уроке мы увидели, как холст можно превратить в мощный редактор изображений, который дает нам некоторые функциональные возможности основных прикладных редакторов изображений, с которыми мы знакомы. Этот пример может быть легко расширен для добавления других функций, таких как обрезка и рисование с помощью инструмента «Карандаш», хотя я оставлю это дополнение для вас. Спасибо за прочтение.