В данной статье будет описано создание части модуля для рисования - paintSprites для редактора Collage_n и за одно будет рассмотрен принцип написания модулей на фреймворке Htmlix. Данный модуль позволяет создавать кисти на основе какого либо спрайта и рисовать ею как в обычном редакторе paint. Таким образом можно отредактировать и вырезать кусочек изображения какой не будь картинки, создать из нее спрайт и рисовать им как обычным карандашом или кистью, а также вращать узор кисти по оси или масштабировать ее колесиком мыши. Например вырезать цветок из какого либо изображения отформатировать его и рисовать.

Редактор работает онлайн, для работы на локальном сервере необходим node.js Для запуска необходимо скопировать файлы редактора, файл модуля paintSprites.js в папку modules, в командной строке из корневой папки запустить node app и перейти по ссылке http://localhost:3000/. После загрузки редактора модуль нужно будет включить во вкладке Загрузить модуль, прописав адрес модуля "./js/modules/paintSprites.js" и нажав кнопку загрузить модуль. Можно также сохранить его в текстовых настройках для автозагрузки в дальнейшем.

Краткий обзор корневого приложения

Collage_n написан на фреймворке htmlix это позволяет загружать модули динамически в процессе работы для удобства. Для начала давайте разберемся с базовыми принципами создания модуля для редактора. В файле interface.js находятся обработчики для кнопок корневого приложения, общие переменные, методы и пользовательские события для всего приложения (в конце файла).

["emiter-create-sprite"] : {prop: ""}, //событие создания спрайта
["emiter-mousemove-canvas"] : {prop: ["x", "y"]}, //событие движения курсора по канвас и массив с координатами точки 
["emiter-mousedown-canvas"] : {prop:  ["x", "y"]}, //событие клика по канвас с координатами
["emiter-mouseup-canvas"] : {prop:  ["x", "y"]},  //событие окончания клика с координатамитами
["emiter-operation-with"] : { //событие смены выполняемой операции и метод для сохранения названия текущей операции в глобальную переменную operationWith
			prop: "common",
			behavior: function(){				
				this.$props().operationWith = this.prop;				
			}		
},

Пользовательские события нужны для того чтобы сообщить всем компонентам которые на них подписаны о каком либо событии корневого приложения. Их можно вызвать из любой точки приложения или в модуле. this.$$("emiter-имя-события").set("данные для подписчиков")

Компонент canvas имеет три метода обработчика событий: mousedown, в нем: обработка клика по канвас для фоновой картинки if(this.$props("operationWith") == "common") либо для спрайта if(this.$props().sprites[this.props("operationWith")]), два других mousemove и mouseup работают аналогично в них мы проверяем значение глобальной переменной "operationWith" если это операция со спрайтом то в данной пременной будет его id.

Остальные файлы: sprites.js - объект для создания и сохранения спрайтов CollageSprite на компьютере, в интерфейсе они представлены массивом sprites и связаны с объектами CollageSprite через глобальную пременную - объект $props().sprites по id, draw_functions.js - просто глобальные функции для работы с canvas: попиксельная обработка, масштабирование, вращение области выделения и изображения и т.д.

Создание модуля

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

//создаем модуль 
//onloadModules - глобальный объект список загружаемых модулей и скриптов main.js
(function(){
   if(onloadModules.paintSprites  != undefined)return;
   var html = `
  		<div data-paint_sprites_panel="container"  class="form-group" name="paint_sprites_panel">
								<label for="exampleFormControlInput1" style="font-size: 15px; color: red; font-weight: bold;">Рисовать</label>
								<div class="form-row">
									  <div class="form-group col-md-4">								
											<button type="button"  style="" name="paint_btn" class="btn btn-danger btn-sm" title="Для рисования нужно выбрать спрайт нажать кнопку и рисовать с нажатой кнопкой мыши">paint</button>
										</div>									
									   <div class="form-group col-md-4">									
										    <input name="draw_sircle_radius" type="text" class="form-control form-control-sm"  placeholder="радиус" title="" value="5">
										</div>
										<div class="form-group col-md-4">									
											<input name="draw_sircle_color" type="text" class="form-control form-control-sm"  placeholder="цвет" title="" value="red">
										</div>										
                 </div>																	
			   </div>
`;
  
//добавление разметки модуля в общюю разметку обычными функциями javascript в любое место
//создали div, нашли куда вставить "[data-main-form]" и перед кем "[name='common_btn_class']"
  var div = document.createElement("div");
  div.innerHTML = html;
  div = div.querySelector("div");
  var parent = document.querySelector("[data-main_form]");
  var insert_before = document.querySelector("[name='common_btns_class']")
  var insertedElement = parent.insertBefore(div, insert_before);
  
  //объект описания модуля  
  var paint_sprites_panel = {
     container: "paint_sprites_panel",
     props: [ ],
     methods: {}
   }
//добавили описание модуля в описание всего приложения.  
HM.description.paint_sprites_panel  = paint_sprites_panel;
//загрузили модуль    
HM.containerInit(div , HM.description, "paint_sprites_panel");
HM.eventProps["emiter-operation-with"].emit(); //вызываем пустым (без параметра) чтобы отключить слушателей canvas событий при старте модуля другими модулями
//поставили флаг для избежания случайной двойной загрузки модуля
onloadModules.paintSprites  = true;
})()

Для того чтобы в пассивном состоянии модуль не прослушивал глобальные события , клика по канвас и перемещения по нему курсора, добавим слушатель события - смены текущей операции operation_with и если модуль не активен != paint-sprites, будем отключать обработчики.

  props: [       		
	  ["canvas_click", "emiter-mousedown-canvas", ""],
    ["canvas_move", "emiter-mousemove-canvas", ""],
    ["mouse_up", "emiter-mouseup-canvas", ""],     
    //Отключать и включать мы их будем в слушателе события 
    ["operation_with", "emiter-operation-with", ""],
]
methods: {
	  operation_with: function(){ //отключает слушателей canvas событий ( mousedown) если модуль находится в пассивном состоянии	  
		  if(this.emiter.prop != "paint-sprites" ){			  				 
			  this.parent.props.canvas_click.disableEvent();// отключаем события
        this.parent.props.canvas_move.disableEvent();
        this.parent.props.mouse_up.disableEvent();
		  }else{	
			  this.parent.props.canvas_click.enableEvent();
        this.parent.props.canvas_move.enableEvent();
        this.parent.props.mouse_ap.enableEvent();
		  }	           		  
	  }
}

Далее добавим обработчик кнопке рисовать

props: [       
      ["paint_btn", "click", "[name='paint_btn']"], //находим кнопку в разметке по ее имени (будет искать относительно контейнера в котором она находится)  

paint_btn: function(){ //добавляем обработчик клику по кнопке 
       //вызываем пользовательское событие с именем операции(имя модуля) чтобы включить обработчики канвас и сказать другим модулям в разных частях приложения отключить свои обработчики или выполнить другие действия (скрыть лишние кнопки и т.д.)ия о
			  this.$$("emiter-operation-with").set("paint-sprites");		 		  		  
	  }	,

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

props:[
    //добавили два свойства для размера и цвета кисти
    //...........................
     ["draw_sircle_radius", "inputvalue", "[name='draw_sircle_radius']"], 
     ["draw_sircle_color", "inputvalue", "[name='draw_sircle_color']"],
]
	 canvas_click: function(){//нажатие кнопки
     //сохранить фоновое изображение до рисования, чтобы можно было отменить functions.js
		  saveStep(saveImg, this.$props().commonProps.area_1);
			ctx.save();
      ctx.putImageData(saveImg, 0, 0);
			////////////////////////
        //рисуем круг данные берем из формы ввода цвета и размера
        //this.emiter.prop доступ к параметру пользовательского события- координаты клика по x и у осям вычисляется в корневом приложении
				ctx.beginPath();
				ctx.arc(this.emiter.prop[0], this.emiter.prop[1], this.parent.props.draw_sircle_radius.getProp(), 0, 2*Math.PI, false);
				ctx.fillStyle =  this.parent.props.draw_sircle_color.getProp();
				ctx.fill();
				ctx.lineWidth = 0.1;
				ctx.strokeStyle =  this.parent.props.draw_sircle_color.getProp();
				ctx.stroke();
        ///сохраняем данные для метода ниже чтобы не брать их постоянно из формы ввода при движении курсора
       	this.parent.props.canvas_move.prop  =  {
						color:  this.parent.props.draw_sircle_color.getProp(),
						radius:  this.parent.props.draw_sircle_radius.getProp(),				   
				}
     
	},
    canvas_move: function(){			
		//тоже самое и при движении курсора мыши
      if(this.prop != null){ //проверяем наличие параметров чтобы не рисовать с когда кнопка поднята
			  ctx.beginPath();
			  ctx.arc(this.emiter.prop[0], this.emiter.prop[1], this.prop.radius, 0, 2*Math.PI, false);
			  ctx.fillStyle =  this.prop.color;
			  ctx.fill();
			  ctx.lineWidth = 0.1;
			  ctx.strokeStyle =  this.prop.color;
			  ctx.stroke();
      }  
	},
	mouse_up: function(){
                //удаляем данные для отмены рисования                   
								this.parent.props.canvas_move.prop = null;
								//сохраняем изображение в глобальную переменную saveImage чтобы можно было нарисовать сверху спрайты, контур и т.д.
                //Для будущих преобразований картинки фона данные берутся из нее (main.js)
								saveImg = ctx.getImageData(0,0, srcWidth, srcHeight);
								ctx.restore();	

		  },

Теперь модуль готов для рисования обычным карандашем, цвета можно задавать любыми удобными форматами "rgba(32, 45, 21, 0.3)", red, #6610f2 и т.д.

Ссылка на похожий модуль: https://github.com/ivanChes2/collagen/blob/main/js/modules/paintSprites.js

Комментарии (0)