Skip to content

Commit d499d13

Browse files
committed
二重ペンを追加
1 parent f194c3f commit d499d13

File tree

9 files changed

+252
-19
lines changed

9 files changed

+252
-19
lines changed
40.9 KB
Loading

03_images/webp.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ffmpeg -i Circle.png -vf scale=100:40 Circle.webp
1+
ffmpeg -i OutlinePen.png -vf OutlnePen.webp

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,10 +1512,10 @@ <h5 data-i18n="comfyUI">ComfyUI</h5>
15121512
<span data-i18n="Pencil">Pencil</span>
15131513
<img src="03_images/preset/brush/Pencil.webp" alt="Pencil Icon" style="width: 100%;">
15141514
</button>
1515-
<!-- <button id="OutlinePenButton" onclick="switchPencilType('OutlinePen')">
1515+
<button id="OutlinePenButton" onclick="switchPencilType('OutlinePen')">
15161516
<span data-i18n="OutlinePen">OutlinePen</span>
15171517
<img src="03_images/preset/brush/OutlinePen.webp" alt="OutlinePen Icon" style="width: 100%;">
1518-
</button> -->
1518+
</button>
15191519
<button id="CircleButton" onclick="switchPencilType('Circle')">
15201520
<span data-i18n="Circle">Circle</span>
15211521
<img src="03_images/preset/brush/Circle.webp" alt="Circle Icon" style="width: 100%;">

js/layer/layer-management.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ var tempCanvas=document.createElement("canvas");
253253
tempCanvas.width=canvasSize;
254254
tempCanvas.height=canvasSize;
255255
var tempCtx=tempCanvas.getContext("2d");
256-
tempCtx.fillStyle="#ffcccc";
256+
tempCtx.fillStyle="#e0e0e0";
257+
tempCtx.fillRect(0,0,canvasSize,canvasSize);
257258

258259
var nowVisible=layer.visible;
259260
layer.visible=true;

js/sidebar/pen/original-brush.js

Lines changed: 232 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,232 @@
1+
fabric.DoubleOutlineBrush=fabric.util.createClass(fabric.BaseBrush,{
2+
type: "DoubleOutlineBrush",
3+
initialize: function(canvas){
4+
this.canvas=canvas;
5+
this.color="#FFFFFF";
6+
this.width=10;
7+
this.outline1Color="#000000";
8+
this.outline1Width=2;
9+
this.outline1Opacity=1;
10+
this.outline2Color="#FF0000";
11+
this.outline2Width=1;
12+
this.outline2Opacity=1;
13+
this.isDrawing=false;
14+
this.currentPathData=null;
15+
this.points=[];
16+
this.pathDataArray=[];
17+
this.outlineImage=null;
18+
this.offscreenCanvas=null;
19+
this.offscreenCtx=null;
20+
},
21+
_initOffscreen: function(){
22+
if(!this.offscreenCanvas){
23+
this.offscreenCanvas=document.createElement("canvas");
24+
this.offscreenCanvas.width=this.canvas.width;
25+
this.offscreenCanvas.height=this.canvas.height;
26+
this.offscreenCtx=this.offscreenCanvas.getContext("2d");
27+
}
28+
},
29+
onMouseDown: function(pointer){
30+
this._initOffscreen();
31+
this.isDrawing=true;
32+
this.points=[pointer];
33+
this.currentPathData={
34+
path:[["M",pointer.x,pointer.y],["L",pointer.x,pointer.y]],
35+
stroke: this.color,
36+
strokeWidth: this.width,
37+
outline1Color: this.outline1Color,
38+
outline1Width: this.outline1Width,
39+
outline1Opacity: this.outline1Opacity,
40+
outline2Color: this.outline2Color,
41+
outline2Width: this.outline2Width,
42+
outline2Opacity: this.outline2Opacity,
43+
};
44+
this._render();
45+
},
46+
onMouseMove: function(pointer){
47+
if(this.isDrawing&&this.currentPathData){
48+
this.points.push(pointer);
49+
if(this.points.length>3){
50+
var lastIndex=this.points.length-1;
51+
var controlX=(this.points[lastIndex].x+this.points[lastIndex-1].x)/2;
52+
var controlY=(this.points[lastIndex].y+this.points[lastIndex-1].y)/2;
53+
this.currentPathData.path.push(["Q",this.points[lastIndex-1].x,this.points[lastIndex-1].y,controlX,controlY]);
54+
this.points.shift();
55+
}else{
56+
this.currentPathData.path[1]=["L",pointer.x,pointer.y];
57+
}
58+
this._render();
59+
}
60+
},
61+
onMouseUp: function(){
62+
if(this.isDrawing){
63+
this.isDrawing=false;
64+
this.pathDataArray.push(this.currentPathData);
65+
this.currentPathData=null;
66+
this.points=[];
67+
this._processOutlines();
68+
}
69+
},
70+
_render: function(){
71+
this.offscreenCtx.clearRect(0,0,this.offscreenCanvas.width,this.offscreenCanvas.height);
72+
var allPaths=this.pathDataArray.slice();
73+
if(this.currentPathData){
74+
allPaths.push(this.currentPathData);
75+
}
76+
var self=this;
77+
allPaths.forEach(function(pathData){
78+
if(pathData.outline2Width>0){
79+
self._drawStroke(pathData,self.offscreenCtx,pathData.strokeWidth+2*pathData.outline1Width+2*pathData.outline2Width,pathData.outline2Color,pathData.outline2Opacity);
80+
}
81+
});
82+
allPaths.forEach(function(pathData){
83+
if(pathData.outline1Width>0){
84+
self._drawStroke(pathData,self.offscreenCtx,pathData.strokeWidth+2*pathData.outline1Width,pathData.outline1Color,pathData.outline1Opacity);
85+
}
86+
});
87+
allPaths.forEach(function(pathData){
88+
self._drawStroke(pathData,self.offscreenCtx,pathData.strokeWidth,pathData.stroke,1);
89+
});
90+
var ctx=this.canvas.contextTop;
91+
ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
92+
ctx.drawImage(this.offscreenCanvas,0,0);
93+
},
94+
_drawStroke: function(pathData,ctx,width,color,opacity){
95+
ctx.save();
96+
ctx.beginPath();
97+
ctx.strokeStyle=color;
98+
ctx.globalAlpha=opacity;
99+
ctx.lineWidth=width;
100+
ctx.lineCap="round";
101+
ctx.lineJoin="round";
102+
pathData.path.forEach(function(segment){
103+
if(segment[0]==="M"){
104+
ctx.moveTo(segment[1],segment[2]);
105+
}else if(segment[0]==="L"){
106+
ctx.lineTo(segment[1],segment[2]);
107+
}else if(segment[0]==="Q"){
108+
ctx.quadraticCurveTo(segment[1],segment[2],segment[3],segment[4]);
109+
}
110+
});
111+
if(pathData.path.length===1){
112+
ctx.lineTo(pathData.path[0][1],pathData.path[0][2]);
113+
}
114+
ctx.stroke();
115+
ctx.restore();
116+
},
117+
_processOutlines: function(){
118+
this.offscreenCtx.clearRect(0,0,this.offscreenCanvas.width,this.offscreenCanvas.height);
119+
var self=this;
120+
this.pathDataArray.forEach(function(pathData){
121+
if(pathData.outline2Width>0){
122+
self._drawStroke(pathData,self.offscreenCtx,pathData.strokeWidth+2*pathData.outline1Width+2*pathData.outline2Width,pathData.outline2Color,pathData.outline2Opacity);
123+
}
124+
});
125+
this.pathDataArray.forEach(function(pathData){
126+
if(pathData.outline1Width>0){
127+
self._drawStroke(pathData,self.offscreenCtx,pathData.strokeWidth+2*pathData.outline1Width,pathData.outline1Color,pathData.outline1Opacity);
128+
}
129+
});
130+
this.pathDataArray.forEach(function(pathData){
131+
self._drawStroke(pathData,self.offscreenCtx,pathData.strokeWidth,pathData.stroke,1);
132+
});
133+
if(this.outlineImage){
134+
this.canvas.remove(this.outlineImage);
135+
}
136+
var tempCanvas=document.createElement("canvas");
137+
tempCanvas.width=this.offscreenCanvas.width;
138+
tempCanvas.height=this.offscreenCanvas.height;
139+
tempCanvas.getContext("2d").drawImage(this.offscreenCanvas,0,0);
140+
this.outlineImage=new fabric.Image(tempCanvas,{
141+
left: 0,
142+
top: 0,
143+
selectable: false,
144+
evented: false,
145+
});
146+
this.canvas.add(this.outlineImage);
147+
this.canvas.contextTop.clearRect(0,0,this.canvas.width,this.canvas.height);
148+
this.canvas.renderAll();
149+
},
150+
mergeDrawings: function(){
151+
if(this.pathDataArray.length===0){
152+
if(this.outlineImage){
153+
this.canvas.remove(this.outlineImage);
154+
this.outlineImage=null;
155+
}
156+
return;
157+
}
158+
this._initOffscreen();
159+
this.offscreenCtx.clearRect(0,0,this.offscreenCanvas.width,this.offscreenCanvas.height);
160+
var self=this;
161+
this.pathDataArray.forEach(function(pathData){
162+
if(pathData.outline2Width>0){
163+
self._drawStroke(pathData,self.offscreenCtx,pathData.strokeWidth+2*pathData.outline1Width+2*pathData.outline2Width,pathData.outline2Color,pathData.outline2Opacity);
164+
}
165+
});
166+
this.pathDataArray.forEach(function(pathData){
167+
if(pathData.outline1Width>0){
168+
self._drawStroke(pathData,self.offscreenCtx,pathData.strokeWidth+2*pathData.outline1Width,pathData.outline1Color,pathData.outline1Opacity);
169+
}
170+
});
171+
this.pathDataArray.forEach(function(pathData){
172+
self._drawStroke(pathData,self.offscreenCtx,pathData.strokeWidth,pathData.stroke,1);
173+
});
174+
var imageData=this.offscreenCtx.getImageData(0,0,this.offscreenCanvas.width,this.offscreenCanvas.height);
175+
var data=imageData.data;
176+
var minX=this.offscreenCanvas.width;
177+
var minY=this.offscreenCanvas.height;
178+
var maxX=0;
179+
var maxY=0;
180+
for(var y=0;y<this.offscreenCanvas.height;y++){
181+
for(var x=0;x<this.offscreenCanvas.width;x++){
182+
var alpha=data[(y*this.offscreenCanvas.width+x)*4+3];
183+
if(alpha>0){
184+
minX=Math.min(minX,x);
185+
minY=Math.min(minY,y);
186+
maxX=Math.max(maxX,x);
187+
maxY=Math.max(maxY,y);
188+
}
189+
}
190+
}
191+
if(maxX<minX||maxY<minY){
192+
changeDoNotSaveHistory();
193+
if(this.outlineImage){
194+
this.canvas.remove(this.outlineImage);
195+
this.outlineImage=null;
196+
}
197+
this.pathDataArray=[];
198+
changeDoSaveHistory();
199+
return;
200+
}
201+
var width=maxX-minX+1;
202+
var height=maxY-minY+1;
203+
var croppedCanvas=document.createElement("canvas");
204+
croppedCanvas.width=width;
205+
croppedCanvas.height=height;
206+
var croppedCtx=croppedCanvas.getContext("2d");
207+
croppedCtx.drawImage(this.offscreenCanvas,minX,minY,width,height,0,0,width,height);
208+
var mergedImage=croppedCanvas.toDataURL();
209+
fabric.Image.fromURL(mergedImage,function(img){
210+
img.set({
211+
left: minX,
212+
top: minY,
213+
selectable: false,
214+
scaleX: 1,
215+
scaleY: 1
216+
});
217+
changeDoNotSaveHistory();
218+
if(self.outlineImage){
219+
self.canvas.remove(self.outlineImage);
220+
self.outlineImage=null;
221+
}
222+
self.pathDataArray=[];
223+
changeDoSaveHistory();
224+
self.canvas.add(img);
225+
self.canvas.renderAll();
226+
});
227+
}
228+
});
229+
1230
fabric.MosaicBrush=fabric.util.createClass(fabric.BaseBrush,{
2231
initialize: function (canvas) {
3232
this.canvas=canvas;
@@ -113,7 +342,9 @@ ctx.moveTo(startX,y);
113342
ctx.lineTo(endX,y);
114343
ctx.stroke();
115344
}
116-
}
345+
},
346+
347+
_render: function(){}
117348

118349
});
119350

js/sidebar/pen/pen-tools.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ canvas.freeDrawingBrush.drawPreviewCircle({x: canvas.width/2,y: canvas.height/2}
3434
setupContextTopBrush(new fabric.PencilBrush(canvas));
3535

3636
} else if (type===MODE_PEN_OUTLINE) {
37-
setupContextTopBrush(new fabric.DoubleOutlineBrush(canvas));
37+
canvas.freeDrawingBrush=new fabric.DoubleOutlineBrush(canvas);
3838

3939
} else if (type===MODE_PEN_TEXTURE) {
4040
setupContextTopBrush(new fabric.PatternBrush(canvas));
@@ -119,7 +119,7 @@ break;
119119
case MODE_PEN_OUTLINE:
120120
settingsHTML+=addColor(MODE_PEN_OUTLINE+'-main-color','color',penValueMap.getOrDefault(MODE_PEN_OUTLINE+'-color','#000000'));
121121
settingsHTML+=addColor(MODE_PEN_OUTLINE+'-outline1-color','outline1-color',penValueMap.getOrDefault(MODE_PEN_OUTLINE+'-outline1-color','#FFFFFF'));
122-
settingsHTML+=addColor(MODE_PEN_OUTLINE+'-outline2-color','outline2-color',penValueMap.getOrDefault(MODE_PEN_OUTLINE+'-outline2-color','#000000'));
122+
settingsHTML+=addColor(MODE_PEN_OUTLINE+'-outline2-color','outline2-color',penValueMap.getOrDefault(MODE_PEN_OUTLINE+'-outline2-color','#FF0000'));
123123

124124
settingsHTML+=addSlider(MODE_PEN_OUTLINE+'-main-width','size',1,150,penValueMap.getOrDefault(MODE_PEN_OUTLINE+'-main-width',10));
125125
settingsHTML+=addSlider(MODE_PEN_OUTLINE+'-outline1-width','outline1-size',1,150,penValueMap.getOrDefault(MODE_PEN_OUTLINE+'-outline1-width',2));
@@ -358,7 +358,7 @@ canvas.freeDrawingBrush.strokeDashArray=[brushWidth,brushWidth*4];
358358
function clearPenActiveButton() {
359359
console.log("clearPenActiveButton is call");
360360
$(MODE_PEN_PENCIL+'Button').classList.remove('active-button');
361-
// $(MODE_PEN_OUTLINE + 'Button').classList.remove('active-button');
361+
$(MODE_PEN_OUTLINE+'Button').classList.remove('active-button');
362362
$(MODE_PEN_CIRCLE+'Button').classList.remove('active-button');
363363
$(MODE_PEN_CRAYON+'Button').classList.remove('active-button');
364364
$(MODE_PEN_INK+'Button').classList.remove('active-button');

js/sidebar/sidebar-ui.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const transLavel=getText(label);
6161
return `
6262
<div class="pen-input-2group">
6363
<label for="${id}" data-i18n="${label}">${transLavel}</label>
64-
<input id="${id}" value="${value}" class="jscolor-color-picker" data-initial-color="rgba(0,0,0,1)">
64+
<input id="${id}" value="${value}" class="jscolor-color-picker" data-initial-color="${value}">
6565
</div>
6666
`;
6767
}

js/ui/third/i18next.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616
//マージされるので日付単位で分けて入れたらOK
1717
const resources = {
1818
"20260111": {
19-
"ja": {"unsupportedProjectFileFormat": "非対応のファイル形式", "unsupportedProjectFileFormatMessage": ".zipまたは.lz4ファイルを選択してください"},
20-
"en": {"unsupportedProjectFileFormat": "Unsupported file format", "unsupportedProjectFileFormatMessage": "Please select a .zip or .lz4 file"},
21-
"ko": {"unsupportedProjectFileFormat": "지원되지 않는 파일 형식", "unsupportedProjectFileFormatMessage": ".zip 또는 .lz4 파일을 선택하세요"},
22-
"fr": {"unsupportedProjectFileFormat": "Format de fichier non pris en charge", "unsupportedProjectFileFormatMessage": "Veuillez sélectionner un fichier .zip ou .lz4"},
23-
"zh": {"unsupportedProjectFileFormat": "不支持的文件格式", "unsupportedProjectFileFormatMessage": "请选择.zip或.lz4文件"},
24-
"ru": {"unsupportedProjectFileFormat": "Неподдерживаемый формат файла", "unsupportedProjectFileFormatMessage": "Пожалуйста, выберите файл .zip или .lz4"},
25-
"es": {"unsupportedProjectFileFormat": "Formato de archivo no compatible", "unsupportedProjectFileFormatMessage": "Por favor seleccione un archivo .zip o .lz4"},
26-
"pt": {"unsupportedProjectFileFormat": "Formato de arquivo não suportado", "unsupportedProjectFileFormatMessage": "Por favor, selecione um arquivo .zip ou .lz4"},
27-
"th": {"unsupportedProjectFileFormat": "รูปแบบไฟล์ไม่รองรับ", "unsupportedProjectFileFormatMessage": "กรุณาเลือกไฟล์ .zip หรือ .lz4"},
28-
"de": {"unsupportedProjectFileFormat": "Nicht unterstütztes Dateiformat", "unsupportedProjectFileFormatMessage": "Bitte wählen Sie eine .zip- oder .lz4-Datei"}
19+
"ja": {"unsupportedProjectFileFormat": "非対応のファイル形式", "unsupportedProjectFileFormatMessage": ".zipまたは.lz4ファイルを選択してください", "OutlinePen": "二重縁取り", "outline1-color": "縁取り1色", "outline2-color": "縁取り2色", "outline1-size": "縁取り1幅", "outline2-size": "縁取り2幅", "outline1-opacity": "縁取り1透明度", "outline2-opacity": "縁取り2透明度"},
20+
"en": {"unsupportedProjectFileFormat": "Unsupported file format", "unsupportedProjectFileFormatMessage": "Please select a .zip or .lz4 file", "OutlinePen": "Double Outline", "outline1-color": "Outline1 Color", "outline2-color": "Outline2 Color", "outline1-size": "Outline1 Width", "outline2-size": "Outline2 Width", "outline1-opacity": "Outline1 Opacity", "outline2-opacity": "Outline2 Opacity"},
21+
"ko": {"unsupportedProjectFileFormat": "지원되지 않는 파일 형식", "unsupportedProjectFileFormatMessage": ".zip 또는 .lz4 파일을 선택하세요", "OutlinePen": "이중 테두리", "outline1-color": "테두리1 색상", "outline2-color": "테두리2 색상", "outline1-size": "테두리1 너비", "outline2-size": "테두리2 너비", "outline1-opacity": "테두리1 불투명도", "outline2-opacity": "테두리2 불투명도"},
22+
"fr": {"unsupportedProjectFileFormat": "Format de fichier non pris en charge", "unsupportedProjectFileFormatMessage": "Veuillez sélectionner un fichier .zip ou .lz4", "OutlinePen": "Double contour", "outline1-color": "Couleur contour1", "outline2-color": "Couleur contour2", "outline1-size": "Largeur contour1", "outline2-size": "Largeur contour2", "outline1-opacity": "Opacité contour1", "outline2-opacity": "Opacité contour2"},
23+
"zh": {"unsupportedProjectFileFormat": "不支持的文件格式", "unsupportedProjectFileFormatMessage": "请选择.zip或.lz4文件", "OutlinePen": "双重描边", "outline1-color": "描边1颜色", "outline2-color": "描边2颜色", "outline1-size": "描边1宽度", "outline2-size": "描边2宽度", "outline1-opacity": "描边1透明度", "outline2-opacity": "描边2透明度"},
24+
"ru": {"unsupportedProjectFileFormat": "Неподдерживаемый формат файла", "unsupportedProjectFileFormatMessage": "Пожалуйста, выберите файл .zip или .lz4", "OutlinePen": "Двойной контур", "outline1-color": "Цвет контура1", "outline2-color": "Цвет контура2", "outline1-size": "Ширина контура1", "outline2-size": "Ширина контура2", "outline1-opacity": "Прозрачность контура1", "outline2-opacity": "Прозрачность контура2"},
25+
"es": {"unsupportedProjectFileFormat": "Formato de archivo no compatible", "unsupportedProjectFileFormatMessage": "Por favor seleccione un archivo .zip o .lz4", "OutlinePen": "Doble contorno", "outline1-color": "Color contorno1", "outline2-color": "Color contorno2", "outline1-size": "Ancho contorno1", "outline2-size": "Ancho contorno2", "outline1-opacity": "Opacidad contorno1", "outline2-opacity": "Opacidad contorno2"},
26+
"pt": {"unsupportedProjectFileFormat": "Formato de arquivo não suportado", "unsupportedProjectFileFormatMessage": "Por favor, selecione um arquivo .zip ou .lz4", "OutlinePen": "Contorno duplo", "outline1-color": "Cor contorno1", "outline2-color": "Cor contorno2", "outline1-size": "Largura contorno1", "outline2-size": "Largura contorno2", "outline1-opacity": "Opacidade contorno1", "outline2-opacity": "Opacidade contorno2"},
27+
"th": {"unsupportedProjectFileFormat": "รูปแบบไฟล์ไม่รองรับ", "unsupportedProjectFileFormatMessage": "กรุณาเลือกไฟล์ .zip หรือ .lz4", "OutlinePen": "เส้นขอบคู่", "outline1-color": "สีขอบ1", "outline2-color": "สีขอบ2", "outline1-size": "ความกว้างขอบ1", "outline2-size": "ความกว้างขอบ2", "outline1-opacity": "ความทึบขอบ1", "outline2-opacity": "ความทึบขอบ2"},
28+
"de": {"unsupportedProjectFileFormat": "Nicht unterstütztes Dateiformat", "unsupportedProjectFileFormatMessage": "Bitte wählen Sie eine .zip- oder .lz4-Datei", "OutlinePen": "Doppelumriss", "outline1-color": "Umriss1 Farbe", "outline2-color": "Umriss2 Farbe", "outline1-size": "Umriss1 Breite", "outline2-size": "Umriss2 Breite", "outline1-opacity": "Umriss1 Deckkraft", "outline2-opacity": "Umriss2 Deckkraft"}
2929
},
3030
"20250413": {
3131
"ja": {"upscaleButton": "画像を高解像度化します", "cropButton": "Crop", "importButton": "インポート", "exitModeButton": "モード解除"},

js/ui/util/mode-change.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ if(MODE_PEN_MOSAIC==type) return true;
6262
if(MODE_PEN_CRAYON==type) return true;
6363
if(MODE_PEN_INK==type) return true;
6464
if(MODE_PEN_MARKER==type) return true;
65+
if(MODE_PEN_OUTLINE==type) return true;
6566
}
6667

6768

0 commit comments

Comments
 (0)