cpp

Рисование на холсте

Тег <canvas>

Тег <canvas> описывает холст на котором мы можем рисовать. Он имеет следующие параметры:

  • width — ширина;
  • height — высота.

Создадим холст, укажем его размеры и зальем зеленым цветом:

<canvas id="canvas" width="400" height="300"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');
   ctx.fillStyle = 'green';
   ctx.fillRect(0, 0, canvas.width, canvas.height);
</script>

Создание контекста рисования

После создания холста нужно получить его контекст рисования. Для этого используется метод getContext(), которому нужно передать значение '2d':

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');

В результате переменная ctx будет ссылаться на экземпляр класса CanvasRenderingContext2D, который содержит свойства и методы, позволяющие рисовать на холсте.

Получить ссылку на объект холста позволяет свойство canvas. Получим размеры холста:

console.log(ctx.canvas.width);  // 400
console.log(ctx.canvas.height); // 300

Изменение характеристик заливки

Управлять цветом заливки позволяет свойство fillStyle. Можно присвоить следующие значения:

  • сплошной цвет, заданный любым способом, поддерживающим CSS:
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ff0000';
ctx.fillRect(10, 10, 30, 30);
ctx.fillStyle = 'rgb(127, 127, 127)';
ctx.fillRect(50, 10, 30, 30);
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(90, 10, 30, 30);
  • объект линейного градиента:
let lg = ctx.createLinearGradient(0, 0, 40, 0);
lg.addColorStop(0, 'black');
lg.addColorStop(1, 'white');
ctx.fillStyle = lg;
ctx.fillRect(10, 50, 30, 30);
  • объект радиального градиента:
let rg = ctx.createRadialGradient(110, 110, 30, 150, 150, 170);
rg.addColorStop(0, 'black');
rg.addColorStop(1, 'white');
ctx.fillStyle = rg;
ctx.fillRect(50, 50, 30, 30);
  • объект текстуры.

Если характеристики заливки не заданы, то используется черный цвет.

Изменение характеристик обводки

Управлять цветом обводки позволяет свойство strokeStyle. Можно присвоить следующие значения:

  • сплошной цвет, заданный любым способом, поддерживающим CSS:
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
ctx.strokeStyle = 'green';
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#ff0000';
ctx.strokeRect(10, 10, 30, 30);
ctx.strokeStyle = 'rgb(127, 127, 127)';
ctx.strokeRect(50, 10, 30, 30);
ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
ctx.strokeRect(90, 10, 30, 30);
  • объект линейного градиента;
  • объект радиального градиента;
  • объект текстуры.

Если цвет обводки не задан, то используется черный цвет.

Дополнительные характеристики задаются с помощью следующих свойств:

  • lineWidth — задает ширину (толщину) обводки. Значение по умолчанию: 1.0. Пример:
ctx.strokeStyle = '#000000';
ctx.lineWidth = 5;
ctx.strokeRect(10, 90, 30, 30);
  • lineCap — задает форму окончания линии. Можно указать следующие значения в виде строки:
  • square — квадратные концы (прибавляются к длине линии); значение по умолчанию;
  • butt — концы никак не оформляются;
  • round — закругленные концы (прибавляются к длине линии).

Пример:

ctx.beginPath();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 15;
ctx.lineCap = 'round';
ctx.moveTo(150, 30);
ctx.lineTo(350, 30);
ctx.stroke();
  • lineJoin — задает форму окончания в месте соединения двух линий обводки. Можно указать следующие значения в виде строки::
  • miter — обычные углы (значение по умолчанию);
  • bevel — скошенные углы;
  • round — закругленные углы.

Пример:

ctx.beginPath();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 15;
ctx.lineJoin = 'bevel';
ctx.moveTo(270, 150);
ctx.lineTo(170, 200);
ctx.lineTo(270, 200);
ctx.stroke();
  • miterLimit — задает ограничение длины угла при использовании стиля miter. Значение по умолчанию: 10. Если значение превышено, то угол будет скошенным.

Следующие методы и свойства делают линию пунктирной:

  • setLineDash(<Массив>) — задает значения для пунктирной линии. Значения указываются в виде массива. Четные индексы задают длину штриха, а нечетные — длину пропуска. Пример рисования пунктирной линии:
ctx.beginPath();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 8;
ctx.setLineDash([15, 10]);
ctx.moveTo(10, 250);
ctx.lineTo(390, 250);
ctx.stroke();

Чтобы сделать следующую линию опять сплошной достаточно передать в метод пустой массив;

  • getLineDash() — возвращает массив со значениями для пунктирной линии;
  • lineDashOffset — задает смещение начала пунктирной обводки. Значение по умолчанию: 0.

Заливка градиентом

Итак, свойства fillStyle и strokeStyle в качестве значения могут принимать объекты линейного или радиального градиентов. Давайте научимся создавать эти объекты.

Линейный градиент

Создать объект линейного градиента позволяет метод createLinearGradient(). Формат метода:

<CanvasGradient> = createLinearGradient(<X1>, <Y1>, <X2>, <Y2>)

Далее с помощью метода addColorStop() объекта CanvasGradient нужно задать точки останова и соответствующие им цвета. Формат метода:

<CanvasGradient>.addColorStop(<Точка останова>, <Цвет>)

В первом параметре указывается значение от 0.0 до 1.0. Во втором параметре задается цвет. Пример создания линейного градиента от черного до белого цветов по горизонтали:

<canvas id="canvas" width="400" height="300"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');

   let lg = ctx.createLinearGradient(0, 0, 400, 0);
   lg.addColorStop(0, 'black');
   lg.addColorStop(1, 'white');
   ctx.fillStyle = lg;
   ctx.fillRect(0, 0, canvas.width, canvas.height);
</script>

Радиальный градиент

Создать объект радиального градиента позволяет метод createRadialGradient(). Формат метода:

<CanvasGradient> = createRadialGradient(<X1>, <Y1>, <R1>,
                                        <X2>, <Y2>, <R2>)

Первые три параметра определяют координаты и радиус внутреннего круга, а последние три — координаты и радиус внешнего круга.

Далее с помощью метода addColorStop() объекта CanvasGradient нужно задать точки останова и соответствующие им цвета. Формат метода:

<CanvasGradient>.addColorStop(<Точка останова>, <Цвет>)

В первом параметре указывается значение от 0.0 (внутренний круг) до 1.0 (внешний круг). Во втором параметре задается цвет. Пример создания радиального градиента от черного до белого цветов:

<canvas id="canvas" width="400" height="300"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');

   let rg = ctx.createRadialGradient(110, 110, 30, 150, 150, 150);
   rg.addColorStop(0, 'black');
   rg.addColorStop(1, 'white');
   ctx.fillStyle = rg;
   ctx.fillRect(0, 0, canvas.width, canvas.height);
</script>

Заливка текстурой

Создать объект текстуры позволяет метод createPattern(). Формат метода:

<Объект текстуры> = createPattern(<Изображение>, <Режим повтора>)

В первом параметре указывается объект изображения, например, ссылка на элемент IMG или экземпляр класса Image. Во втором параметре задается режим повтора изображения в виде строки:

  • repeat — повтор по горизонтали и вертикали;
  • repeat-x — повтор только по горизонтали;
  • repeat-y — повтор только по вертикали;
  • no-repeat — без повтора.

Пример:

<canvas id="canvas" width="800" height="800"></canvas>
<img src="texture.jpg" alt="" id="img1">
<script>
   window.onload = function() {
      let canvas = document.getElementById('canvas');
      let ctx = canvas.getContext('2d');
      let img = document.getElementById('img1');

      ctx.fillStyle = ctx.createPattern(img, 'repeat');
      ctx.fillRect(0, 0, canvas.width, canvas.height);
   };
</script>

Если используется экземпляр класса Image, то нужно дождаться загрузки текстуры:

<canvas id="canvas" width="800" height="800"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');

   let img = new Image();
   img.onload = function () {
      ctx.fillStyle = ctx.createPattern(img, 'repeat');
      ctx.fillRect(0, 0, canvas.width, canvas.height);
   };
   img.src = 'texture.jpg';
</script>

Рисование траектории

Управлять рисованием траектории позволяют следующие методы:

  • beginPath() — начинает новую траекторию;
  • moveTo(<X>, <Y>) — позволяет переместить текущую позицию в точку с указанными координатами;
  • closePath() — позволяет замкнуть текущую траекторию;
  • stroke() — прорисовывает текущую траекторию, используя характеристики обводки (см. разд. 10.4);
  • fill([<Правила>]) — прорисовывает текущую траекторию, используя характеристики заливки (см. разд. 10.3). В качестве параметра можно указать алгоритмы заливки nonzero (значение по умолчанию) или evenodd в виде строк.

Пример рисования треугольника с обводкой и заливкой:

ctx.fillStyle = 'green';
ctx.strokeStyle = 'red';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(20, 100);
ctx.lineTo(100, 100);
ctx.closePath();
ctx.stroke();
ctx.fill();
  • lineTo() — добавляет прямую линию к текущей траектории:
ctx.lineTo(20, 100);
  • arcTo() — добавляет дугу к текущей траектории. Формат метода:
arcTo(<X1>, <Y1>, <X2>, <Y2>, <radius>)

Пример рисования дуги:

ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(120, 120);
ctx.arcTo(200, 0, 300, 100, 100);
ctx.stroke();
  • arc() — добавляет дугу к текущей траектории. Формат метода:
arc(<centerX>, <centerY>, <radius>, <startAngle>,
    <endAngle>[, <Направление>])

Параметры <centerX> и <centerY> задают координаты центра круга, параметр <radius> — радиус круга, параметр <startAngle> — начальный угол в радианах, а параметр <endAngle> — конечный угол в радианах. Если параметр <Направление> имеет значение true, то дуга рисуется против часовой стрелки, а если false — по часовой стрелке.

Формула преобразования градусов в радианы:

radians = (Math.PI / 180) * degrees;

Пример:

ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(300, 150);
ctx.arc(200, 150, 100, 0, (Math.PI / 180) * 135);
ctx.stroke();
  • bezierCurveTo() — добавляет кубическую кривую Безье к текущей траектории. Формат метода:
bezierCurveTo(<xc1>, <yc1>, <xc2>, <yc2>, <X1>, <Y1>)

Параметры <xc1> и <yc1> задают координаты первой опорной точки, параметры <xc2> и <yc2> — координаты второй опорной точки, а параметры <X1> и <Y1> — координаты конечной точки. Пример:

ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(295, 225);
ctx.bezierCurveTo(200, 390, 200, 200, 350, 300);
ctx.stroke();
  • quadraticCurveTo() — добавляет квадратичную кривую к текущей траектории. Формат метода:
quadraticCurveTo(<xc>, <yc>, <X1>, <Y1>)

Параметры <xc> и <yc> задают координаты опорной точки, а параметры <X1> и <Y1> — координаты конечной точки. Пример:

ctx.strokeStyle = 'green';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(50, 400);
ctx.quadraticCurveTo(200, 300, 350, 400);
ctx.stroke();
  • rect() — добавляет к траектории прямоугольник. Этот метод удобно использовать совместно с методом clip() для добавления маски. Формат метода:
rect(<X>, <Y>, <Ширина>, <Высота>)

Пример:

ctx.strokeStyle = 'green';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.rect(300, 50, 80, 80);
ctx.stroke();
  • clip() — создает маску на основе текущего пути. Область внутри пути будет видна, а вне — скрыта.

Иногда бывает необходимо выяснить, входит ли точка с заданными координатами в состав траектории. Сделать это можно с помощью метода isPointInPath(<X>, <Y>[, <fillRule>]). Метод возвращает true, если точка с такими координатами входит в состав траектории, и false — в противном случае.

Рисование прямоугольников

Нарисовать на холсте прямоугольники позволяют следующие методы:

  • fillRect() — рисует прямоугольник, используя характеристики заливки (см. разд. 10.3). Формат метода:
fillRect(<X>, <Y>, <Ширина>, <Высота>)

Параметры <X> и <Y> задают координаты левого верхнего угла прямоугольника, а параметры <Ширина> и <Высота> — ширину и высоту. Пример:

ctx.fillStyle = 'green';
ctx.fillRect(50, 50, 200, 100);
  • strokeRect() — рисует прямоугольник, используя характеристики обводки (см. разд. 10.4). Формат метода:
strokeRect(<X>, <Y>, <Ширина>, <Высота>)

Пример:

ctx.strokeStyle = 'blue';
ctx.lineWidth = 5;
ctx.strokeRect(50, 200, 80, 80);

Вывод текста

Управлять характеристиками текста позволяют следующие свойства:

  • font — задает характеристики шрифта:
ctx.font = '16pt Verdana, Tahoma, sans-serif';
ctx.fillStyle = 'green';
ctx.fillText('Electron', 50, 50);

Получим значение по умолчанию:

console.log(ctx.font); // 10px sans-serif
  • textAlign — задает горизонтальное выравнивание текста относительно начальной точки. Возможные значения в виде строки: start, end, left, right или center. Получим значение по умолчанию:
console.log(ctx.textAlign); // start

Пример:

ctx.font = '20pt Arial';
ctx.fillStyle = 'red';
ctx.textAlign = 'center';
ctx.fillText('Electron', 250, 50);
  • textBaseline — задает вертикальное выравнивание текста относительно начальной точки. Возможные значения в виде строки: alphabetic, ideographic, top, middle, hanging или bottom. Получим значение по умолчанию:
console.log(ctx.textBaseline); // alphabetic

Пример выравнивания по верху:

ctx.textBaseline = 'top';
ctx.fillText('Electron', 250, 100);

Вывести текст позволяют следующие методы:

  • fillText() — выводит текст, используя характеристики заливки (см. разд. 10.3). Формат метода:
fillText(<text>, <X>, <Y>[, <maxWidth>])

Параметр <text> задает выводимый текст, параметры <X> и <X> — координаты начальной точки, а параметр <maxWidth> — максимальную ширину поля. Пример ограничения ширины поля:

ctx.font = '16pt Verdana, Tahoma, sans-serif';
ctx.fillStyle = 'blue';
ctx.fillText('Electron', 50, 100, 50);

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

  • strokeText() — выводит текст, используя характеристики обводки (см. разд. 10.4). Формат метода:
strokeText(<text>, <X>, <Y>[, <maxWidth>])

Пример:

ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.textAlign = 'start';
ctx.font = '48pt Verdana';
ctx.strokeText('Electron', 50, 200);

Узнать информацию о характеристиках блока, в который будет вписан текст, позволяет метод measureText(<Текст>). Он возвращает объект TextMetrics. С помощью свойства width этого объекта, мы можем получить ширину текста в пикселах:

let text = 'Electron';
ctx.font = '48pt Verdana';
console.log(ctx.measureText(text).width); // 261.375

Вывод изображения

Вывести изображение на холст позволяет метод drawImage(). Форматы метода:

drawImage(<img>, <X>, <Y>)
drawImage(<img>, <X>, <Y>, <w>, <h>)
drawImage(<img>, <sx>, <sy>, <sw>, <sh>,
                 <dx>, <dy>, <dw>, <dh>)

Первый формат выводит изображение полностью в позицию с координатами <X> и <Y>. В первом параметре указывается объект изображения, например, ссылка на элемент IMG или экземпляр класса Image. Пример указания ссылки на элемент IMG:

<canvas id="canvas" width="800" height="800"></canvas>
<img src="photo.jpg" alt="" id="img1">
<script>
   window.onload = function() {
      let canvas = document.getElementById('canvas');
      let ctx = canvas.getContext('2d');
      let img = document.getElementById('img1');
      ctx.drawImage(img, 0, 0);
   };
</script>

Второй формат позволяет ограничить область вывода шириной (параметр <w>) и высотой (параметр <h>). При этом изображение может быть уменьшено или увеличено таким образом, чтобы вписаться в область. Если пропорции области не совпадают с пропорциями изображения, то изображение будет растянуто или сжато без соблюдения пропорций. Пример:

ctx.drawImage(img, 0, 400, 250, 166);

Третий формат берет прямоугольную область (<sx>, <sy>, <sw>, <sh>) с изображения и вписывает ее в прямоугольную область (<dx>, <dy>, <dw>, <dh>) на холсте. Пример указания экземпляра класса Image:

let img2 = new Image();
img2.onload = function() {
   ctx.drawImage(img2, 200,  50, 300, 300,
                       300, 400, 300, 300);
};
img2.src = 'photo.jpg';

Очистка прямоугольной области или всего холста

Стереть какую-либо прямоугольную область на холсте позволяет метод clearRect(). Формат метода:

clearRect(<X>, <Y>, <Ширина>, <Высота>)

Пример очистки всего холста:

ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

Сохранение и восстановление состояния

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

  • save() — сохраняет значения основных характеристик (в частности характеристики заливки, обводки, шрифта и др.) в стек. Полный список сохраняемых характеристик можно найти в документации;
  • restore() — восстанавливает значения основных характеристик из стека.

Ограничим область вывода длинного текста маской:

ctx.save();
ctx.beginPath();
ctx.rect(0, 0, 270, 200);
ctx.clip();
ctx.font = '48pt Verdana';
ctx.fillText('Electron', 50, 50);
ctx.restore();

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

Применение трансформаций

Применить различные преобразования позволяют следующие свойства и методы:

  • globalAlpha — задает степень непрозрачности (вещественное число от 0.0 (полностью прозрачный) до 1.0 (полностью непрозрачный)). Значение по умолчанию: 1.0. Пример:
ctx.save();
ctx.fillStyle = 'green';
ctx.globalAlpha = 0.5;
ctx.fillRect(10, 10, 20, 20);
ctx.restore();
  • scale(<dx>, <dy>) — изменяет масштабирование. Пример увеличения масштаба в два раза:
ctx.save();
ctx.scale(2.0, 2.0);
ctx.fillRect(50, 10, 20, 20);
ctx.restore();
  • rotate(<Угол в радианах>) — применяет трансформацию вращения. Пример вращения прямоугольника на 45 градусов относительно левого верхнего угла холста:
ctx.save();
ctx.fillStyle = 'blue';
ctx.rotate((Math.PI / 180) * 45);
ctx.fillRect(50, -20, 40, 40);
ctx.restore();

Формула преобразования градусов в радианы:

radians = (Math.PI / 180) * degrees;
  • translate(<tx>, <ty>) — сдвигает систему координат. Пример вращения прямоугольника на 45 градусов относительно точки вставки:
ctx.save();
ctx.fillStyle = 'green';
ctx.translate(200, 200);
ctx.rotate((Math.PI / 180) * 45);
ctx.fillRect(0, 0, 40, 40);
ctx.restore();
Примечание

Режимы наложения

При наложении фигур по умолчанию пиксели верхней фигуры перекроют пиксели нижней фигуры. Такое поведение задается режимом наложения source-over. С помощью свойства globalCompositeOperation можно указать другой режим наложения. Возможны следующие значения в виде строки: source-over, source-in, source-out, source-atop, destination-over, destination-in, destination-out, destination-atop, lighter, copy, xor, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity. Пример указания значения:

ctx.fillStyle = 'blue';
ctx.fillRect(0, 50, 400, 200);
ctx.fillStyle = 'red';
ctx.globalCompositeOperation = 'source-in';
ctx.fillRect(100, 0, 200, 300);

Создание тени

Управлять характеристиками тени позволяют следующие свойства:

  • shadowBlur — степень размытия тени (значение по умолчанию: 0);
  • shadowColor — цвет тени (значение по умолчанию: черный цвет);
  • shadowOffsetX — смещение тени по горизонтали (значение по умолчанию: 0);
  • shadowOffsetY — смещение тени по вертикали (значение по умолчанию: 0).

Пример создания квадрата с тенью:

<canvas id="canvas" width="400" height="300"></canvas>
<script>
   let canvas = document.getElementById('canvas');
   let ctx = canvas.getContext('2d');

   ctx.shadowBlur = 15;
   ctx.shadowColor = 'black';
   ctx.shadowOffsetX = 5;
   ctx.shadowOffsetY = 5;
   
   ctx.fillStyle = 'blue';
   ctx.fillRect(50, 50, 100, 100);
</script>

Манипулирование отдельными пикселями

Выполнить манипуляции отдельными пикселами позволяют следующие методы:

  • getImageData() — возвращает объект ImageData, содержащий данные в виде массива с указанного фрагмента холста. Формат метода:
getImageData(<X>, <Y>, <Ширина>, <Высота>)

Пример копирования всех данных с холста:

let data1 = ctx.getImageData(0, 0, canvas.width, canvas.height);
console.log(data1.width, data1.height);
  • createImageData() — возвращает объект ImageData, содержащий данные в виде массива (все пикселы будут иметь прозрачный черный цвет). Форматы метода:
createImageData(<Ширина>, <Высота>)
createImageData(<ImageData>)

Пример:

let data2 = ctx.createImageData(100, 100);
console.log(data2.width, data2.height);    // 100 100
console.log(data2.data[0], data2.data[1],
            data2.data[2], data2.data[3]); // 0 0 0 0

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

data2 = ctx.createImageData(data1);
console.log(data2.width, data2.height);     // 400 300
console.log(data2.data[0], data2.data[1],
            data2.data[2], data2.data[3]);  // 0 0 0 0
  • putImageData() — выводит данные из объекта <ImageData> на холст. Формат метода:
putImageData(<ImageData>, <X1>, <Y1>[, 
                          <X2>, <Y2>, <Ширина>, <Высота>])

Параметры <X1> и <Y1> задают начальную позицию вставки на холсте. Параметры <X2> и <Y2> определяют начальные координаты внутри объекта <ImageData>, а параметры <Ширина> и <Высота> — размеры копируемой области.

Класс ImageData содержит следующие свойства:

  • width — ширина изображения;
  • height — высота изображения;
  • data — одномерный массив с данными (объект Uint8ClampedArray). Каждый пиксел кодируется четырьмя числами от 0 до 255. Первое число задает долю красного цвета, второе — долю зеленого цвета, третье — долю синего цвета, а четвертое — альфа-канал. Нумерация пикселов в массиве идет слева направо и сверху вниз, т. е. по строкам. Получить размер массива позволяет свойство length.

Создадим пустой массив, заполним его красным цветом, а затем выведем на холст:

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
// ...
data2 = ctx.createImageData(canvas.width, canvas.height);
console.log(data2.width, data2.height); // 400 300
console.log(data2.data[0], data2.data[1], data2.data[2], data2.data[3]);
// 0 0 0 0
for (let i = 0; i < data2.data.length; i += 4) {
   data2.data[i + 0] = 255; // R
   data2.data[i + 1] = 0;   // G
   data2.data[i + 2] = 0;   // B
   data2.data[i + 3] = 255; // A
}
let canvas2 = document.getElementById('canvas2');
let ctx2 = canvas2.getContext('2d');
ctx2.putImageData(data2, 0, 0);

Метод toDataURL()

Метод toDataURL() объекта холста возвращает URL с закодированными данными изображения в указанном формате. Формат метода:

toDataURL([<Тип>[, <Качество JPEG>]])

В первом параметре можно указать MIME-тип изображения. Если параметр не указан, то используется значение image/png:

let dataPNG = canvas.toDataURL();
console.log(dataPNG);
// data:image/png;base64,iVBORw0KGgoAAAANSUhEU...

Во втором параметре можно указать качество JPEG-изображения в виде вещественного числа от 0 до 1. Значение по умолчанию: 0.92. Пример:

let dataJPEG = canvas.toDataURL('image/jpeg', 0.92);
console.log(dataJPEG);
// data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA...

При использовании формата JPEG следует учитывать, что сжатие выполняется с потерями, поэтому можно получить различные артефакты на изображении. Кроме того, формат JPEG не поддерживает прозрачность.

Сохранение изображения в файл

Метод toBlob() объекта холста возвращает объект Blob с данными изображения в указанном формате. Формат метода:

toBlob(<Функция>[, <Тип>[, <Качество JPEG>]])

В первом параметре указывается ссылка на функцию, которая будет вызвана при завершении операции. Через параметр внутри функции будет доступен объект Blob. Во втором параметре можно указать MIME-тип изображения. Если параметр не указан, то используется значение image/png. В третьем параметре можно указать качество JPEG-изображения в виде вещественного числа от 0 до 1. Пример:

canvas.toBlob( function(blob) {
   console.log(blob);
   // Blob {size: 2826, type: "image/jpeg"}
}, 'image/jpeg', 0.92);

Для преобразования объекта Blob в объект ArrayBuffer используется метод arrayBuffer(), который возвращает объект Promise<ArrayBuffer>. Чтобы из объекта ArrayBuffer получить объект Buffer, следует воспользоваться статическим методом from(). Пример сохранения изображения с холста в файл при нажатии кнопки:

let btn1 = document.getElementById('btn1');
btn1.addEventListener('click', (e) => {
   const fs = require('fs');
   const path = require('path');

   canvas.toBlob( async function(blob) {
      let buf = Buffer.from(await blob.arrayBuffer());
      let p = path.join(__dirname, 'test.png');
      try {
         fs.writeFileSync(p, buf, {encoding: null});
         console.log('Сохранено');
      } catch (e) {
         console.log(e);
      }
   }, 'image/png');
});

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

let btn3 = document.getElementById('btn3');
btn3.addEventListener('click', (e) => {
   const fs = require('fs');
   const path = require('path');

   let p = path.join(__dirname, 'test.png');
   if ( !fs.existsSync(p) ) {
      console.log('Файл не найден');
      return;
   }

   let img = new Image();
   img.onload = function() {
      ctx.drawImage(img, 0, 0);
   };
   img.src = p;
});

Учебник по Electron js
Учебник Electron js в формате PDF

Реквизиты

ЮMoney: 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов

cpp