2025-06-18 11:05:23 +08:00
/ * *
* fabric . brushes - A collection of brushes for fabric . js ( version 4 and up ) .
*
* Made by Arjan Haverkamp , https : //www.webgear.nl
* Copyright 2021 Arjan Haverkamp
* MIT Licensed
* @ version 1.0 - 2021 - 06 - 02
* @ url https : //github.com/av01d/fabric-brushes
*
* Inspiration sources :
* - https : //github.com/tennisonchan/fabric-brush
* - https : //mrdoob.com/projects/harmony/
* - http : //perfectionkills.com/exploring-canvas-drawing-techniques/
* /
2025-07-14 23:42:28 +08:00
import { fabric } from "fabric-with-all" ;
2025-06-18 11:05:23 +08:00
import { sprayBrushDataUrl } from "./data/sprayBrushData.js" ;
( function ( fabric ) {
/ * *
2025-06-22 13:52:28 +08:00
* Trim a canvas . Returns the left - top coordinate where trimming began .
2025-06-18 11:05:23 +08:00
* @ param { canvas } canvas A canvas element to trim . This element will be trimmed ( reference ) .
* @ returns { Object } Left - top coordinate of trimmed area . Example : { x : 65 , y : 104 }
* @ see : https : //stackoverflow.com/a/22267731/3360038
* /
fabric . util . trimCanvas = function ( canvas ) {
var ctx = canvas . getContext ( "2d" ) ,
w = canvas . width ,
h = canvas . height ,
pix = { x : [ ] , y : [ ] } ,
n ,
imageData = ctx . getImageData ( 0 , 0 , w , h ) ,
fn = function ( a , b ) {
return a - b ;
} ;
for ( var y = 0 ; y < h ; y ++ ) {
for ( var x = 0 ; x < w ; x ++ ) {
if ( imageData . data [ ( y * w + x ) * 4 + 3 ] > 0 ) {
pix . x . push ( x ) ;
pix . y . push ( y ) ;
}
}
}
pix . x . sort ( fn ) ;
pix . y . sort ( fn ) ;
n = pix . x . length - 1 ;
//if (n == -1) {
// // Nothing to trim... empty canvas?
//}
w = pix . x [ n ] - pix . x [ 0 ] ;
h = pix . y [ n ] - pix . y [ 0 ] ;
if ( ! w ) {
return ;
}
var cut = ctx . getImageData ( pix . x [ 0 ] , pix . y [ 0 ] , w , h ) ;
canvas . width = w ;
canvas . height = h ;
ctx . putImageData ( cut , 0 , 0 ) ;
return { x : pix . x [ 0 ] , y : pix . y [ 0 ] } ;
} ;
/ * *
* Extract r , g , b , a components from any valid color .
* Returns { undefined } when color cannot be parsed .
*
* @ param { number } color Any color string ( named , hex , rgb , rgba )
* @ returns { ( Array | undefined ) } Example : [ 0 , 128 , 255 , 1 ]
* @ see https : //gist.github.com/oriadam/396a4beaaad465ca921618f2f2444d49
* /
fabric . util . colorValues = function ( color ) {
if ( ! color ) {
return ;
}
if ( color . toLowerCase ( ) === "transparent" ) {
return [ 0 , 0 , 0 , 0 ] ;
}
if ( color [ 0 ] === "#" ) {
if ( color . length < 7 ) {
// convert #RGB and #RGBA to #RRGGBB and #RRGGBBAA
color =
"#" +
color [ 1 ] +
color [ 1 ] +
color [ 2 ] +
color [ 2 ] +
color [ 3 ] +
color [ 3 ] +
( color . length > 4 ? color [ 4 ] + color [ 4 ] : "" ) ;
}
return [
parseInt ( color . substr ( 1 , 2 ) , 16 ) ,
parseInt ( color . substr ( 3 , 2 ) , 16 ) ,
parseInt ( color . substr ( 5 , 2 ) , 16 ) ,
color . length > 7 ? parseInt ( color . substr ( 7 , 2 ) , 16 ) / 255 : 1 ,
] ;
}
if ( color . indexOf ( "rgb" ) === - 1 ) {
// convert named colors
2025-07-14 01:00:23 +08:00
var tempElem = document . body . appendChild ( document . createElement ( "fictum" ) ) ; // intentionally use unknown tag to lower chances of css rule override with !important
2025-06-18 11:05:23 +08:00
var flag = "rgb(1, 2, 3)" ; // this flag tested on chrome 59, ff 53, ie9, ie10, ie11, edge 14
tempElem . style . color = flag ;
if ( tempElem . style . color !== flag ) {
return ; // color set failed - some monstrous css rule is probably taking over the color of our object
}
tempElem . style . color = color ;
if ( tempElem . style . color === flag || tempElem . style . color === "" ) {
return ; // color parse failed
}
color = getComputedStyle ( tempElem ) . color ;
document . body . removeChild ( tempElem ) ;
}
if ( color . indexOf ( "rgb" ) === 0 ) {
if ( color . indexOf ( "rgba" ) === - 1 ) {
color += ",1" ; // convert 'rgb(R,G,B)' to 'rgb(R,G,B)A' which looks awful but will pass the regxep below
}
2025-07-14 01:00:23 +08:00
return color . match ( /[.\d]+/g ) . map ( function ( a ) {
2025-06-18 11:05:23 +08:00
return + a ;
} ) ;
}
} ;
fabric . Point . prototype . angleBetween = function ( that ) {
return Math . atan2 ( this . x - that . x , this . y - that . y ) ;
} ;
fabric . Point . prototype . normalize = function ( thickness ) {
if ( null === thickness || undefined === thickness ) {
thickness = 1 ;
}
var length = this . distanceFrom ( { x : 0 , y : 0 } ) ;
if ( length > 0 ) {
this . x = ( this . x / length ) * thickness ;
this . y = ( this . y / length ) * thickness ;
}
return this ;
} ;
/ * *
* Convert a brush drawing on the upperCanvas to an image on the fabric canvas .
* This makes the drawing editable , it can be moved , rotated , scaled , skewed etc .
* /
fabric . BaseBrush . prototype . convertToPath = function ( ) {
var pixelRatio = this . canvas . getRetinaScaling ( ) ,
c = fabric . util . copyCanvasElement ( this . canvas . upperCanvasEl ) ,
xy = fabric . util . trimCanvas ( c ) ,
path = this . _points
. map ( ( arr ) => {
arr [ 1 ] = arr [ 1 ] / this . canvas . getZoom ( ) ;
arr [ 2 ] = arr [ 2 ] / this . canvas . getZoom ( ) ;
return arr . join ( " " ) ;
} )
. join ( " " ) ,
pathElemetn = new fabric . Path ( path ) ;
if ( ! xy ) {
return ;
}
let pointerX = this . canvas . viewportTransform [ 4 ] ;
let pointerY = this . canvas . viewportTransform [ 5 ] ;
pathElemetn
. set ( {
strokeDashArray : [ this . _width * 3 , this . _width * 3 ] ,
strokeWidth : this . _width ,
stroke : "black" ,
fill : "transparent" ,
custom : {
dashed : true ,
} ,
} )
. setCoords ( ) ;
let group = new fabric . Group ( [ pathElemetn ] , {
left : ( xy . x / pixelRatio - pointerX ) / this . canvas . getZoom ( ) ,
top : ( xy . y / pixelRatio - pointerY ) / this . canvas . getZoom ( ) ,
custom : {
dashed : true ,
} ,
} ) ;
this . canvas . add ( group ) . clearContext ( this . canvas . contextTop ) ;
this . canvas . clearContext ( this . canvas . contextTop ) ;
} ;
fabric . BaseBrush . prototype . convertToImg = function ( ) {
var pixelRatio = this . canvas . getRetinaScaling ( ) ,
c = fabric . util . copyCanvasElement ( this . canvas . upperCanvasEl ) ,
xy = fabric . util . trimCanvas ( c ) ,
img = new fabric . Image ( c ) ;
if ( ! xy ) {
return ;
}
let pointerX = this . canvas . viewportTransform [ 4 ] ;
let pointerY = this . canvas . viewportTransform [ 5 ] ;
img
. set ( {
left : ( xy . x / pixelRatio - pointerX ) / this . canvas . getZoom ( ) ,
top : ( xy . y / pixelRatio - pointerY ) / this . canvas . getZoom ( ) ,
scaleX : 1 / pixelRatio / this . canvas . getZoom ( ) ,
scaleY : 1 / pixelRatio / this . canvas . getZoom ( ) ,
custom : {
type : "pencil" ,
} ,
} )
. setCoords ( ) ;
// 检查是否有图像处理回调函数
const result = this . canvas ? . onBrushImageConverted ? . ( img ) ;
! result && this . canvas . add ( img ) ;
this . canvas . clearContext ( this . canvas . contextTop ) ;
} ;
fabric . util . getRandom = function ( max , min ) {
min = min ? min : 0 ;
return Math . random ( ) * ( ( max ? max : 1 ) - min ) + min ;
} ;
fabric . util . clamp = function ( n , max , min ) {
if ( typeof min !== "number" ) {
min = 0 ;
}
return n > max ? max : n < min ? min : n ;
} ;
fabric . Stroke = fabric . util . createClass ( fabric . Object , {
color : null ,
inkAmount : null ,
lineWidth : null ,
_point : null ,
_lastPoint : null ,
_currentLineWidth : null ,
initialize : function ( ctx , pointer , range , color , lineWidth , inkAmount ) {
var rx = fabric . util . getRandom ( range ) ,
c = fabric . util . getRandom ( Math . PI * 2 ) ,
c0 = fabric . util . getRandom ( Math . PI * 2 ) ,
x0 = rx * Math . sin ( c0 ) ,
y0 = ( rx / 2 ) * Math . cos ( c0 ) ,
cos = Math . cos ( c ) ,
sin = Math . sin ( c ) ;
this . ctx = ctx ;
this . color = color ;
this . _point = new fabric . Point (
pointer . x + x0 * cos - y0 * sin ,
pointer . y + x0 * sin + y0 * cos
) ;
this . lineWidth = lineWidth ;
this . inkAmount = inkAmount ;
this . _currentLineWidth = lineWidth ;
ctx . lineCap = "round" ;
} ,
update : function ( pointer , subtractPoint , distance ) {
this . _lastPoint = fabric . util . object . clone ( this . _point ) ;
this . _point = this . _point . addEquals ( {
x : subtractPoint . x ,
y : subtractPoint . y ,
} ) ;
var n = this . inkAmount / ( distance + 1 ) ,
per = n > 0.3 ? 0.2 : n < 0 ? 0 : n ;
this . _currentLineWidth = this . lineWidth * per ;
} ,
draw : function ( ) {
var ctx = this . ctx ;
ctx . save ( ) ;
2025-07-14 01:00:23 +08:00
this . line ( ctx , this . _lastPoint , this . _point , this . color , this . _currentLineWidth ) ;
2025-06-18 11:05:23 +08:00
ctx . restore ( ) ;
} ,
line : function ( ctx , point1 , point2 , color , lineWidth ) {
ctx . strokeStyle = color ;
ctx . lineWidth = lineWidth ;
ctx . beginPath ( ) ;
ctx . moveTo ( point1 . x , point1 . y ) ;
ctx . lineTo ( point2 . x , point2 . y ) ;
ctx . stroke ( ) ;
} ,
} ) ;
/ * *
* CrayonBrush
* Based on code by Tennison Chan .
* /
fabric . CrayonBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 0.6 ,
width : 30 ,
_baseWidth : 15 ,
_inkAmount : 10 ,
_latestStrokeLength : 0 ,
_point : null ,
_sep : 3 ,
_size : 0 ,
_latest : null ,
_drawn : false ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || this . opacity ;
this . _baseWidth = this . width / 2 ;
this . _point = new fabric . Point ( 0 , 0 ) ;
} ,
onMouseDown : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . canvas . contextTop . globalAlpha = this . opacity ;
this . _size = this . width / 2 + this . _baseWidth ;
this . _drawn = false ;
this . set ( pointer ) ;
} ,
onMouseMove : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . update ( pointer ) ;
this . draw ( this . canvas . contextTop ) ;
} ,
onMouseUp : function ( ) {
if ( this . _drawn ) {
this . convertToImg ( ) ;
}
this . _latest = null ;
this . _latestStrokeLength = 0 ;
this . canvas . contextTop . globalAlpha = 1 ;
} ,
set : function ( p ) {
if ( this . _latest ) {
this . _latest . setFromPoint ( this . _point ) ;
} else {
this . _latest = new fabric . Point ( p . x , p . y ) ;
}
fabric . Point . prototype . setFromPoint . call ( this . _point , p ) ;
} ,
update : function ( p ) {
this . set ( p ) ;
2025-07-14 01:00:23 +08:00
this . _latestStrokeLength = this . _point . subtract ( this . _latest ) . distanceFrom ( { x : 0 , y : 0 } ) ;
2025-06-18 11:05:23 +08:00
} ,
draw : function ( ctx ) {
var i , j , p , r , c , x , y , w , h , v , s , stepNum , dotSize , dotNum , range ;
v = this . _point . subtract ( this . _latest ) ;
s = Math . ceil ( this . _size / 2 ) ;
stepNum = Math . floor ( v . distanceFrom ( { x : 0 , y : 0 } ) / s ) + 1 ;
v . normalize ( s ) ;
dotSize =
2025-07-14 01:00:23 +08:00
this . _sep * fabric . util . clamp ( ( this . _inkAmount / this . _latestStrokeLength ) * 3 , 1 , 0.5 ) ;
2025-06-18 11:05:23 +08:00
dotNum = Math . ceil ( this . _size * this . _sep ) ;
range = this . _size / 2 ;
ctx . save ( ) ;
ctx . fillStyle = this . color ;
ctx . beginPath ( ) ;
for ( i = 0 ; i < dotNum ; i ++ ) {
for ( j = 0 ; j < stepNum ; j ++ ) {
p = this . _latest . add ( v . multiply ( j ) ) ;
r = fabric . util . getRandom ( range ) ;
c = fabric . util . getRandom ( Math . PI * 2 ) ;
w = fabric . util . getRandom ( dotSize , dotSize / 2 ) ;
h = fabric . util . getRandom ( dotSize , dotSize / 2 ) ;
x = p . x + r * Math . sin ( c ) - w / 2 ;
y = p . y + r * Math . cos ( c ) - h / 2 ;
ctx . rect ( x , y , w , h ) ;
}
}
ctx . fill ( ) ;
ctx . restore ( ) ;
this . _drawn = true ;
} ,
_render : function ( ) { } ,
} ) ; // End CrayonBrush
/ * *
* FurBrush
* Based on code by Mr . Doob .
* /
fabric . FurBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 1 ,
_count : 0 ,
_points : [ ] ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || 1 ;
} ,
onMouseDown : function ( pointer ) {
2025-06-29 23:29:47 +08:00
// 添加坐标转换处理画布缩放和偏移
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-29 23:29:47 +08:00
2025-06-18 11:05:23 +08:00
this . _points = [ pointer ] ;
this . _count = 0 ;
var ctx = this . canvas . contextTop ,
color = fabric . util . colorValues ( this . color ) ;
ctx . strokeStyle =
2025-07-14 01:00:23 +08:00
"rgba(" + color [ 0 ] + "," + color [ 1 ] + "," + color [ 2 ] + "," + 0.1 * this . opacity + ")" ;
2025-06-18 11:05:23 +08:00
ctx . lineWidth = this . width ;
this . _points . push ( pointer ) ;
} ,
onMouseMove : function ( pointer ) {
2025-06-29 23:29:47 +08:00
// 添加坐标转换处理画布缩放和偏移
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-29 23:29:47 +08:00
2025-06-18 11:05:23 +08:00
this . _points . push ( pointer ) ;
var i ,
dx ,
dy ,
d ,
ctx = this . canvas . contextTop ,
points = this . _points ,
lastPoint = points [ points . length - 2 ] ;
ctx . beginPath ( ) ;
ctx . moveTo ( lastPoint . x , lastPoint . y ) ;
ctx . lineTo ( pointer . x , pointer . y ) ;
ctx . stroke ( ) ;
for ( i = 0 ; i < this . _points . length ; i ++ ) {
dx = this . _points [ i ] . x - this . _points [ this . _count ] . x ;
dy = this . _points [ i ] . y - this . _points [ this . _count ] . y ;
d = dx * dx + dy * dy ;
if ( d < 2000 && Math . random ( ) > d / 2000 ) {
ctx . beginPath ( ) ;
ctx . moveTo ( pointer . x + dx * 0.5 , pointer . y + dy * 0.5 ) ;
ctx . lineTo ( pointer . x - dx * 0.5 , pointer . y - dy * 0.5 ) ;
ctx . stroke ( ) ;
}
}
this . _count ++ ;
} ,
onMouseUp : function ( pointer ) {
if ( this . _count > 0 ) {
this . convertToImg ( ) ;
}
} ,
_render : function ( ) { } ,
} ) ; // End FurBrush
/ * *
* InkBrush
* Based on code by Tennison Chan .
* /
fabric . InkBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 30 ,
_baseWidth : 15 ,
_inkAmount : 7 ,
_lastPoint : null ,
_point : null ,
_range : 10 ,
_strokes : null ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || this . opacity ;
this . _baseWidth = this . width ;
this . _point = new fabric . Point ( ) ;
} ,
_render : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
var len ,
i ,
point = this . setPointer ( pointer ) ,
subtractPoint = point . subtract ( this . _lastPoint ) ,
distance = point . distanceFrom ( this . _lastPoint ) ,
stroke ;
for ( i = 0 , len = this . _strokes . length ; i < len ; i ++ ) {
stroke = this . _strokes [ i ] ;
stroke . update ( point , subtractPoint , distance ) ;
stroke . draw ( ) ;
}
if ( distance > 30 ) {
this . drawSplash ( point , this . _inkAmount ) ;
}
} ,
onMouseDown : function ( pointer ) {
this . canvas . contextTop . globalAlpha = this . opacity ;
this . _resetTip ( pointer ) ;
} ,
onMouseMove : function ( pointer ) {
if ( this . canvas . _isCurrentlyDrawing ) {
this . _render ( pointer ) ;
}
} ,
onMouseUp : function ( ) {
this . convertToImg ( ) ;
this . canvas . contextTop . globalAlpha = 1 ;
} ,
hexToRgba : function ( hex , alpha ) { } ,
drawSplash : function ( pointer , maxSize ) {
var c ,
r ,
i ,
point ,
ctx = this . canvas . contextTop ,
num = fabric . util . getRandom ( 12 ) ,
range = maxSize * 10 ,
color = this . color ;
ctx . save ( ) ;
for ( i = 0 ; i < num ; i ++ ) {
r = fabric . util . getRandom ( range , 1 ) ;
c = fabric . util . getRandom ( Math . PI * 2 ) ;
2025-07-14 01:00:23 +08:00
point = new fabric . Point ( pointer . x + r * Math . sin ( c ) , pointer . y + r * Math . cos ( c ) ) ;
2025-06-18 11:05:23 +08:00
ctx . fillStyle = color ;
ctx . beginPath ( ) ;
2025-07-14 01:00:23 +08:00
ctx . arc ( point . x , point . y , fabric . util . getRandom ( maxSize ) / 2 , 0 , Math . PI * 2 , false ) ;
2025-06-18 11:05:23 +08:00
ctx . fill ( ) ;
}
ctx . restore ( ) ;
} ,
setPointer : function ( pointer ) {
var point = new fabric . Point ( pointer . x , pointer . y ) ;
this . _lastPoint = fabric . util . object . clone ( this . _point ) ;
this . _point = point ;
return point ;
} ,
_resetTip : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
var len ,
i ,
point = this . setPointer ( pointer ) ;
this . _strokes = [ ] ;
this . size = this . width ;
this . _range = this . size / 2 ;
for ( i = 0 , len = this . size ; i < len ; i ++ ) {
this . _strokes [ i ] = new fabric . Stroke (
this . canvas . contextTop ,
point ,
this . _range ,
this . color ,
this . width + 10 ,
this . _inkAmount
) ;
}
} ,
} ) ; // End InkBrush
/ * *
* LongfurBrush
* Based on code by Mr . Doob .
* /
fabric . LongfurBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 1 ,
_count : 0 ,
_points : [ ] ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || 1 ;
} ,
onMouseDown : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _points = [ pointer ] ;
this . _count = 0 ;
var ctx = this . canvas . contextTop ,
color = fabric . util . colorValues ( this . color ) ;
//ctx.globalCompositeOperation = 'source-over';
ctx . strokeStyle =
2025-07-14 01:00:23 +08:00
"rgba(" + color [ 0 ] + "," + color [ 1 ] + "," + color [ 2 ] + "," + 0.05 * this . opacity + ")" ;
2025-06-18 11:05:23 +08:00
ctx . lineWidth = this . width ;
} ,
onMouseMove : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _points . push ( pointer ) ;
var i ,
dx ,
dy ,
d ,
size ,
ctx = this . canvas . contextTop ,
points = this . _points ;
for ( i = 0 ; i < this . _points . length ; i ++ ) {
size = - Math . random ( ) ;
dx = this . _points [ i ] . x - this . _points [ this . _count ] . x ;
dy = this . _points [ i ] . y - this . _points [ this . _count ] . y ;
d = dx * dx + dy * dy ;
if ( d < 1000 && Math . random ( ) > d / 1000 ) {
ctx . beginPath ( ) ;
ctx . moveTo (
this . _points [ this . _count ] . x + dx * size ,
this . _points [ this . _count ] . y + dy * size
) ;
ctx . lineTo (
this . _points [ i ] . x - dx * size + Math . random ( ) * 2 ,
this . _points [ i ] . y - dy * size + Math . random ( ) * 2
) ;
ctx . stroke ( ) ;
}
}
this . _count ++ ;
} ,
onMouseUp : function ( pointer ) {
if ( this . _count > 0 ) {
this . convertToImg ( ) ;
}
} ,
_render : function ( ) { } ,
} ) ; // End LongfurBrush
/ * *
* WritingBrush
* Based on code by Tennison Chan .
* /
fabric . WritingBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 30 ,
_baseWidth : 15 ,
_lastPoint : null ,
_lineWidth : 2 ,
_point : null ,
_size : 0 ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || this . opacity ;
this . _baseWidth = this . width ;
this . canvas . contextTop . globalAlpha = this . opacity ;
this . _point = new fabric . Point ( ) ;
this . canvas . contextTop . lineJoin = "round" ;
this . canvas . contextTop . lineCap = "round" ;
} ,
_render : function ( pointer ) {
var ctx , lineWidthDiff , i , len ;
ctx = this . canvas . contextTop ;
ctx . beginPath ( ) ;
// let num = this._size / this._lineWidth / 2 / 1.2
let num = this . width / 1.25 / 2 ;
for ( i = 0 , len = this . width / 1.25 ; i < len ; i ++ ) {
// for(i = 0, len = (this._size / this._lineWidth) / 1.2; i < len; i++) {
lineWidthDiff = ( this . _lineWidth - 1 ) * i ;
ctx . globalAlpha = 0.8 * this . opacity ;
ctx . moveTo (
this . _lastPoint . x - lineWidthDiff + num ,
this . _lastPoint . y + lineWidthDiff - num
) ;
2025-07-14 01:00:23 +08:00
ctx . lineTo ( pointer . x - lineWidthDiff + num , pointer . y + lineWidthDiff - num ) ;
2025-06-18 11:05:23 +08:00
ctx . stroke ( ) ;
}
this . _lastPoint = new fabric . Point ( pointer . x , pointer . y ) ;
} ,
onMouseDown : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _lastPoint = pointer ;
this . canvas . contextTop . strokeStyle = this . color ;
this . canvas . contextTop . lineWidth = this . _lineWidth ;
this . _size = this . width + this . _baseWidth ;
} ,
onMouseMove : function ( pointer ) {
if ( this . canvas . _isCurrentlyDrawing ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _render ( pointer ) ;
}
} ,
onMouseUp : function ( ) {
this . canvas . contextTop . globalAlpha = this . opacity ;
this . canvas . contextTop . globalAlpha = 1 ;
this . convertToImg ( ) ;
} ,
} ) ; // End WritingBrush
/ * *
* MarkerBrush
* Based on code by Tennison Chan .
* /
fabric . MarkerBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 30 ,
_baseWidth : 15 ,
_lastPoint : null ,
_lineWidth : 2 ,
_point : null ,
_size : 0 ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || this . opacity ;
this . _baseWidth = this . width ;
this . canvas . contextTop . globalAlpha = this . opacity ;
this . _point = new fabric . Point ( ) ;
this . canvas . contextTop . lineJoin = "round" ;
this . canvas . contextTop . lineCap = "round" ;
} ,
_render : function ( pointer ) {
var ctx , lineWidthDiff , i , len ;
ctx = this . canvas . contextTop ;
ctx . beginPath ( ) ;
let num = this . width / 1.25 / 2 ;
for ( i = 0 , len = this . width / 1.25 ; i < len ; i ++ ) {
lineWidthDiff = ( this . _lineWidth - 1 ) * i ;
ctx . globalAlpha = 0.8 * this . opacity ;
ctx . moveTo (
this . _lastPoint . x + lineWidthDiff - num ,
this . _lastPoint . y + lineWidthDiff - num
) ;
2025-07-14 01:00:23 +08:00
ctx . lineTo ( pointer . x + lineWidthDiff - num , pointer . y + lineWidthDiff - num ) ;
2025-06-18 11:05:23 +08:00
ctx . stroke ( ) ;
}
this . _lastPoint = new fabric . Point ( pointer . x , pointer . y ) ;
} ,
onMouseDown : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _lastPoint = pointer ;
this . canvas . contextTop . strokeStyle = this . color ;
this . canvas . contextTop . lineWidth = this . _lineWidth ;
this . _size = this . width + this . _baseWidth / 2 ;
} ,
onMouseMove : function ( pointer ) {
if ( this . canvas . _isCurrentlyDrawing ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _render ( pointer ) ;
}
} ,
onMouseUp : function ( ) {
this . canvas . contextTop . globalAlpha = this . opacity ;
this . canvas . contextTop . globalAlpha = 1 ;
this . convertToImg ( ) ;
} ,
} ) ; // End MarkerBrush
/ * *
* test
* Based on code by Tennison Chan .
* /
fabric . Test = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
_points : [ ] ,
_width : 2 ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . _width = opt . _width || this . _width ;
this . color = opt . color || this . color ;
} ,
_render : function ( pointer ) {
var ctx , lineWidthDiff , i , len ;
ctx = this . canvas . contextTop ;
this . _points . push ( [ "L" , pointer . x , pointer . y ] ) ;
// if(this._points.length % 10 < 5){
let points = this . _points ;
ctx . beginPath ( ) ;
ctx . moveTo ( points [ points . length - 2 ] [ 1 ] , points [ points . length - 2 ] [ 2 ] ) ;
ctx . lineTo ( points [ points . length - 1 ] [ 1 ] , points [ points . length - 1 ] [ 2 ] ) ;
ctx . stroke ( ) ;
// }
} ,
onMouseDown : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _points = [ ] ;
this . _points . push ( [ "M" , pointer . x , pointer . y ] ) ;
var ctx = this . canvas . contextTop ;
ctx . strokeStyle = "rgba(" + 0 + "," + 0 + "," + 0 + "," + 1 + ")" ;
ctx . lineWidth = this . _width * this . canvas . getZoom ( ) ;
ctx . lineJoin = ctx . lineCap = "round" ;
} ,
onMouseMove : function ( pointer ) {
if ( this . canvas . _isCurrentlyDrawing ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _render ( pointer ) ;
}
} ,
onMouseUp : function ( ) {
this . _points . push ( [ "Z" ] ) ;
this . convertToPath ( ) ;
} ,
} ) ; // End test
/ * *
* MarkerBrush
* Based on code by Tennison Chan .
* /
fabric . MarkerBrush1 = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 30 ,
content : 0 ,
_baseWidth : 15 ,
_lastPoint : null ,
_lineWidth : 2 ,
_point : null ,
_size : 0 ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || this . opacity ;
this . _baseWidth = this . width ;
this . content = opt . content || canvas . width / 2 ;
this . canvas . contextTop . globalAlpha = this . opacity ;
this . _point = new fabric . Point ( ) ;
this . canvas . contextTop . lineJoin = "round" ;
this . canvas . contextTop . lineCap = "round" ;
} ,
_render : function ( pointer ) {
var ctx , lineWidthDiff , i , len ;
ctx = this . canvas . contextTop ;
ctx . beginPath ( ) ;
for ( i = 0 , len = this . _size / this . _lineWidth / 2 ; i < len ; i ++ ) {
lineWidthDiff = ( this . _lineWidth - 1 ) * i ;
let half = this . canvas . width / 2 ;
ctx . globalAlpha = 0.8 * this . opacity ;
ctx . moveTo ( this . _lastPoint . x , this . _lastPoint . y ) ;
let x =
2025-06-29 23:29:47 +08:00
this . content > this . _lastPoint . x
? this . content - this . _lastPoint . x + this . content
: this . content * 2 - this . _lastPoint . x ;
2025-06-18 11:05:23 +08:00
ctx . lineTo ( x , this . _lastPoint . y ) ;
// ctx.lineTo(pointer.y + lineWidthDiff,pointer.x + lineWidthDiff);
ctx . stroke ( ) ;
}
this . _lastPoint = new fabric . Point ( pointer . x , pointer . y ) ;
} ,
onMouseDown : function ( pointer ) {
2025-06-29 23:29:47 +08:00
// 添加坐标转换处理画布缩放和偏移
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-29 23:29:47 +08:00
2025-06-18 11:05:23 +08:00
this . _lastPoint = pointer ;
this . canvas . contextTop . strokeStyle = this . color ;
this . canvas . contextTop . lineWidth = this . _lineWidth ;
this . _size = this . width + this . _baseWidth ;
} ,
onMouseMove : function ( pointer ) {
if ( this . canvas . _isCurrentlyDrawing ) {
2025-06-29 23:29:47 +08:00
// 添加坐标转换处理画布缩放和偏移
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-29 23:29:47 +08:00
2025-06-18 11:05:23 +08:00
this . _render ( pointer ) ;
}
} ,
onMouseUp : function ( ) {
this . canvas . contextTop . globalAlpha = this . opacity ;
this . canvas . contextTop . globalAlpha = 1 ;
this . convertToImg ( ) ;
} ,
2025-06-29 23:29:47 +08:00
} ) ; // End MarkerBrush1
2025-06-18 11:05:23 +08:00
/ * *
* PenBrush
* Based on code by Tennison Chan .
* /
fabric . PenBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 30 ,
_baseWidth : 15 ,
_lastPoint : null ,
_lineWidth : 2 ,
_point : null ,
_size : 0 ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || this . opacity ;
this . _baseWidth = this . width ;
this . canvas . contextTop . globalAlpha = this . opacity ;
this . _point = new fabric . Point ( ) ;
this . canvas . contextTop . lineJoin = "round" ;
this . canvas . contextTop . lineCap = "round" ;
} ,
_render : function ( pointer ) {
var ctx , lineWidthDiff , i , len ;
ctx = this . canvas . contextTop ;
ctx . beginPath ( ) ;
// let num = this._size / this._lineWidth / 2 / 1.2
let num = this . width / 1.25 / 2 ;
for ( i = 0 , len = this . width / 1.25 ; i < len ; i ++ ) {
// for(i = 0, len = (this._size / this._lineWidth) / 1.2; i < len; i++) {
var randomNum = Math . random ( ) * ( 0.6 - 0.2 ) + 0.2 ;
var color = this . color . replace ( /1(?=\))/ , randomNum ) ;
this . canvas . contextTop . strokeStyle = color ;
lineWidthDiff = ( this . _lineWidth - 1 ) * i ;
ctx . globalAlpha = 0.8 * this . opacity ;
ctx . moveTo ( this . _lastPoint . x , this . _lastPoint . y + lineWidthDiff - num ) ;
ctx . lineTo ( pointer . x , pointer . y + lineWidthDiff - num ) ;
ctx . stroke ( ) ;
}
this . _lastPoint = new fabric . Point ( pointer . x , pointer . y ) ;
} ,
onMouseDown : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _lastPoint = pointer ;
this . canvas . contextTop . lineWidth = this . _lineWidth ;
this . _size = this . width + this . _baseWidth ;
} ,
onMouseMove : function ( pointer ) {
if ( this . canvas . _isCurrentlyDrawing ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _render ( pointer ) ;
}
} ,
onMouseUp : function ( ) {
this . canvas . contextTop . globalAlpha = this . opacity ;
this . canvas . contextTop . globalAlpha = 1 ;
this . convertToImg ( ) ;
} ,
} ) ; // End PenBrush
/ * *
* RibbonBrush
* Based on code by Mr . Doob .
* /
fabric . RibbonBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 1 ,
_nrPainters : 50 ,
_painters : [ ] ,
_lastPoint : null ,
_interval : null ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . _nrPainters = opt . _nrPainters || this . _nrPainters ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || this . opacity ;
for ( var i = 0 ; i < this . _nrPainters ; i ++ ) {
this . _painters . push ( {
dx : this . canvas . width / 2 ,
dy : this . canvas . height / 2 ,
ax : 0 ,
ay : 0 ,
div : 0.1 ,
ease : Math . random ( ) * 0.2 + 0.6 ,
} ) ;
}
} ,
update : function ( ) {
var ctx = this . canvas . contextTop ,
painters = this . _painters ;
for ( var i = 0 ; i < painters . length ; i ++ ) {
ctx . beginPath ( ) ;
ctx . moveTo ( painters [ i ] . dx , painters [ i ] . dy ) ;
painters [ i ] . dx -= painters [ i ] . ax =
2025-07-14 01:00:23 +08:00
( painters [ i ] . ax + ( painters [ i ] . dx - this . _lastPoint . x ) * painters [ i ] . div ) *
2025-06-18 11:05:23 +08:00
painters [ i ] . ease ;
painters [ i ] . dy -= painters [ i ] . ay =
2025-07-14 01:00:23 +08:00
( painters [ i ] . ay + ( painters [ i ] . dy - this . _lastPoint . y ) * painters [ i ] . div ) *
2025-06-18 11:05:23 +08:00
painters [ i ] . ease ;
ctx . lineTo ( painters [ i ] . dx , painters [ i ] . dy ) ;
ctx . stroke ( ) ;
}
} ,
onMouseDown : function ( pointer ) {
var ctx = this . canvas . contextTop ,
color = fabric . util . colorValues ( this . color ) ;
this . _painters = [ ] ;
for ( var i = 0 ; i < this . _nrPainters ; i ++ ) {
this . _painters . push ( {
dx : this . canvas . width / 2 ,
dy : this . canvas . height / 2 ,
ax : 0 ,
ay : 0 ,
div : 0.1 ,
ease : Math . random ( ) * 0.2 + 0.6 ,
} ) ;
}
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _lastPoint = pointer ;
//ctx.globalCompositeOperation = 'source-over';
ctx . strokeStyle =
2025-07-14 01:00:23 +08:00
"rgba(" + color [ 0 ] + "," + color [ 1 ] + "," + color [ 2 ] + "," + 0.05 * this . opacity + ")" ;
2025-06-18 11:05:23 +08:00
ctx . lineWidth = this . width ;
2025-07-14 01:00:23 +08:00
for ( let i = 0 ; i < this . _nrPainters ; i ++ ) {
2025-06-18 11:05:23 +08:00
this . _painters [ i ] . dx = pointer . x ;
this . _painters [ i ] . dy = pointer . y ;
}
var self = this ;
this . _interval = setInterval ( function ( ) {
self . update ( ) ;
} , 1000 / 60 ) ;
} ,
onMouseMove : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _lastPoint = pointer ;
} ,
onMouseUp : function ( pointer ) {
clearInterval ( this . _interval ) ;
this . convertToImg ( ) ;
} ,
_render : function ( ) { } ,
} ) ; // End RibbonBrush
/ * *
* ShadedBrush
* Based on code by Mr . Doob .
* /
fabric . ShadedBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 0.3 ,
width : 1 ,
shadeDistance : 1000 ,
_points : [ ] ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || this . opacity ;
this . shadeDistance = opt . shadeDistance || 1000 ;
} ,
onMouseDown : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _points = [ pointer ] ;
var ctx = this . canvas . contextTop ,
color = fabric . util . colorValues ( this . color ) ;
ctx . strokeStyle =
2025-07-14 01:00:23 +08:00
"rgba(" + color [ 0 ] + "," + color [ 1 ] + "," + color [ 2 ] + "," + this . opacity + ")" ;
2025-06-18 11:05:23 +08:00
ctx . lineWidth = this . width ;
ctx . lineJoin = ctx . lineCap = "round" ;
} ,
onMouseMove : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _points . push ( pointer ) ;
var ctx = this . canvas . contextTop ,
points = this . _points ,
dx ,
dy ,
d ; // 在此处声明变量
ctx . beginPath ( ) ;
ctx . moveTo ( points [ points . length - 2 ] . x , points [ points . length - 2 ] . y ) ;
ctx . lineTo ( points [ points . length - 1 ] . x , points [ points . length - 1 ] . y ) ;
ctx . stroke ( ) ;
for ( var i = 0 , len = points . length ; i < len ; i ++ ) {
dx = points [ i ] . x - points [ points . length - 1 ] . x ;
dy = points [ i ] . y - points [ points . length - 1 ] . y ;
d = dx * dx + dy * dy ;
if ( d < this . shadeDistance ) {
ctx . beginPath ( ) ;
ctx . moveTo (
points [ points . length - 1 ] . x + dx * 0.2 ,
points [ points . length - 1 ] . y + dy * 0.2
) ;
ctx . lineTo ( points [ i ] . x - dx * 0.2 , points [ i ] . y - dy * 0.2 ) ;
ctx . stroke ( ) ;
}
}
} ,
onMouseUp : function ( pointer ) {
if ( this . _points . length > 1 ) {
this . convertToImg ( ) ;
}
} ,
_render : function ( ) { } ,
} ) ; // End ShadedBrush
/ * *
* SketchyBrush
* Based on code by Mr . Doob .
* /
fabric . SketchyBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 1 ,
_count : 0 ,
_points : [ ] ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || this . opacity ;
} ,
onMouseDown : function ( pointer ) {
this . canvas . contextTop . globalAlpha = this . opacity ;
// 坐标转换
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _points = [ pointer ] ;
this . _count = 0 ;
this . _drawn = false ;
// 设置绘图样式
var ctx = this . canvas . contextTop ;
var color = fabric . util . colorValues ( this . color ) ;
ctx . lineWidth = this . width ;
ctx . strokeStyle =
2025-07-14 01:00:23 +08:00
"rgba(" + color [ 0 ] + "," + color [ 1 ] + "," + color [ 2 ] + "," + 0.3 * this . opacity + ")" ;
2025-06-18 11:05:23 +08:00
} ,
onMouseMove : function ( pointer ) {
// 坐标转换
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _points . push ( pointer ) ;
var i , dx , dy , d ;
var factor = 0.3 * this . width ;
var ctx = this . canvas . contextTop ;
var points = this . _points ;
var lastPoint = points . length > 1 ? points [ points . length - 2 ] : points [ 0 ] ; // 修复第一次无上一个点的问题
// 绘制主线条
ctx . beginPath ( ) ;
ctx . moveTo ( lastPoint . x , lastPoint . y ) ;
ctx . lineTo ( pointer . x , pointer . y ) ;
ctx . stroke ( ) ;
// 增加透明度设置
var color = fabric . util . colorValues ( this . color ) ;
ctx . strokeStyle =
2025-07-14 01:00:23 +08:00
"rgba(" + color [ 0 ] + "," + color [ 1 ] + "," + color [ 2 ] + "," + 0.2 * this . opacity + ")" ;
2025-06-18 11:05:23 +08:00
// 修改循环逻辑,确保在有点时能画出效果
if ( this . _count > 0 ) {
// 确保有历史点可用
for ( i = 0 ; i < points . length ; i ++ ) {
dx = points [ i ] . x - points [ this . _count ] . x ;
dy = points [ i ] . y - points [ this . _count ] . y ;
d = dx * dx + dy * dy ;
if ( d < 4000 && Math . random ( ) > d / 2000 ) {
ctx . beginPath ( ) ;
2025-07-14 01:00:23 +08:00
ctx . moveTo ( points [ this . _count ] . x + dx * factor , points [ this . _count ] . y + dy * factor ) ;
2025-06-18 11:05:23 +08:00
ctx . lineTo ( points [ i ] . x - dx * factor , points [ i ] . y - dy * factor ) ;
ctx . stroke ( ) ;
this . _drawn = true ;
}
}
}
this . _count ++ ;
} ,
onMouseUp : function ( pointer ) {
if ( this . _count > 0 || this . _drawn ) {
this . convertToImg ( ) ;
}
this . canvas . contextTop . globalAlpha = 1 ;
} ,
_render : function ( ) { } ,
} ) ; // End SketchyBrush
/ * *
* SpraypaintBrush
* Based on code by Tennison Chan .
* /
fabric . SpraypaintBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 30 ,
_baseWidth : 40 ,
_inkAmount : 0 ,
_interval : 20 ,
_lastPoint : null ,
_point : null ,
brush : null ,
sprayBrushDataUrl : sprayBrushDataUrl ,
initialize : function ( canvas , opt ) {
var self = this ;
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . opacity = opt . opacity || this . opacity ;
this . color = opt . color || this . color ;
this . canvas . contextTop . lineJoin = "round" ;
this . canvas . contextTop . lineCap = "round" ;
this . _reset ( ) ;
fabric . Image . fromURL (
this . sprayBrushDataUrl ,
function ( brush ) {
self . brush = brush ;
self . brush . filters = [ ] ;
self . _setColor ( self . color || this . color ) ;
} ,
{ crossOrigin : "anonymous" }
) ;
} ,
_setColor : function ( color ) {
this . color = color ;
this . brush . filters [ 0 ] = new fabric . Image . filters . BlendColor ( {
color : color ,
alpha : 1 ,
mode : "tint" ,
} ) ;
this . brush . applyFilters ( ) ;
} ,
onMouseDown : function ( pointer ) {
this . canvas . contextTop . globalAlpha = this . opacity ;
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _point = new fabric . Point ( pointer . x , pointer . y ) ;
this . _lastPoint = this . _point ;
// this.size = this.width + this._baseWidth;
this . size = this . width + this . width / 2 ;
this . _inkAmount = 0 ;
this . _setColor ( this . color ) ;
this . _render ( ) ;
} ,
onMouseMove : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _lastPoint = this . _point ;
this . _point = new fabric . Point ( pointer . x , pointer . y ) ;
} ,
onMouseUp : function ( ) {
var self = this ;
setTimeout ( function ( ) {
self . convertToImg ( ) ;
self . _reset ( ) ;
} , this . _interval ) ;
} ,
_render : function ( ) {
var self = this ;
function draw ( ) {
var point , distance , angle , amount , x , y ;
point = new fabric . Point ( self . _point . x || 0 , self . _point . y || 0 ) ;
distance = point . distanceFrom ( self . _lastPoint ) ;
angle = point . angleBetween ( self . _lastPoint ) ;
amount = 100 / self . size / ( Math . pow ( distance , 2 ) + 1 ) ;
self . _inkAmount += amount ;
self . _inkAmount = Math . max ( self . _inkAmount - distance / 10 , 0 ) ;
x = self . _lastPoint . x + Math . sin ( angle ) - self . size / 2 ;
y = self . _lastPoint . y + Math . cos ( angle ) - self . size / 2 ;
2025-07-14 01:00:23 +08:00
self . canvas . contextTop . drawImage ( self . brush . _element , x , y , self . size , self . size ) ;
2025-06-18 11:05:23 +08:00
if ( self . canvas . _isCurrentlyDrawing ) {
setTimeout ( draw , self . _interval ) ;
} else {
self . _reset ( ) ;
}
}
draw ( ) ;
} ,
_reset : function ( ) {
this . _point = null ;
this . _lastPoint = null ;
this . canvas . contextTop . globalAlpha = 1 ;
} ,
} ) ; // End SpraypaintBrush
/ * *
* SquaresBrush
* Based on code by Mr . Doob .
* /
fabric . SquaresBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
bgColor : "#fff" ,
opacity : 1 ,
width : 1 ,
_lastPoint : null ,
_drawn : false ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . bgColor = opt . bgColor || "#fff" ;
this . opacity = opt . opacity || this . opacity ;
} ,
onMouseDown : function ( pointer ) {
var ctx = this . canvas . contextTop ,
color = fabric . util . colorValues ( this . color ) ,
bgColor = fabric . util . colorValues ( this . bgColor ) ;
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _lastPoint = pointer ;
this . _drawn = false ;
//ctx.globalCompositeOperation = 'source-over';
this . canvas . contextTop . globalAlpha = this . opacity ;
ctx . fillStyle =
2025-07-14 01:00:23 +08:00
"rgba(" + bgColor [ 0 ] + "," + bgColor [ 1 ] + "," + bgColor [ 2 ] + "," + bgColor [ 3 ] + ")" ;
ctx . strokeStyle = "rgba(" + color [ 0 ] + "," + color [ 1 ] + "," + color [ 2 ] + "," + color [ 3 ] + ")" ;
2025-06-18 11:05:23 +08:00
ctx . lineWidth = this . width ;
} ,
onMouseMove : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
var ctx = this . canvas . contextTop ,
dx = pointer . x - this . _lastPoint . x ,
dy = pointer . y - this . _lastPoint . y ,
angle = 1.57079633 ,
px = Math . cos ( angle ) * dx - Math . sin ( angle ) * dy ,
py = Math . sin ( angle ) * dx + Math . cos ( angle ) * dy ;
ctx . beginPath ( ) ;
ctx . moveTo ( this . _lastPoint . x - px , this . _lastPoint . y - py ) ;
ctx . lineTo ( this . _lastPoint . x + px , this . _lastPoint . y + py ) ;
ctx . lineTo ( pointer . x + px , pointer . y + py ) ;
ctx . lineTo ( pointer . x - px , pointer . y - py ) ;
ctx . lineTo ( this . _lastPoint . x - px , this . _lastPoint . y - py ) ;
ctx . fill ( ) ;
ctx . stroke ( ) ;
this . _lastPoint = pointer ;
this . _drawn = true ;
} ,
onMouseUp : function ( pointer ) {
if ( this . _drawn ) {
this . convertToImg ( ) ;
}
this . canvas . contextTop . globalAlpha = 1 ;
} ,
_render : function ( ) { } ,
} ) ; // End SquaresBrush
/ * *
* WebBrush
* Based on code by Mr . Doob .
* /
fabric . WebBrush = fabric . util . createClass ( fabric . BaseBrush , {
color : "#000" ,
opacity : 1 ,
width : 1 ,
_count : 0 ,
_points : [ ] ,
initialize : function ( canvas , opt ) {
opt = opt || { } ;
this . canvas = canvas ;
this . width = opt . width || this . width ;
this . color = opt . color || this . color ;
this . opacity = opt . opacity || 1 ;
} ,
onMouseDown : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _points = [ pointer ] ;
this . _count = 0 ;
this . _colorValues = fabric . util . colorValues ( this . color ) ;
} ,
onMouseMove : function ( pointer ) {
2025-07-14 01:00:23 +08:00
pointer . x = pointer . x * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 4 ] ;
pointer . y = pointer . y * this . canvas . getZoom ( ) + this . canvas . viewportTransform [ 5 ] ;
2025-06-18 11:05:23 +08:00
this . _points . push ( pointer ) ;
var ctx = this . canvas . contextTop ,
points = this . _points ,
lastPoint = points [ points . length - 2 ] ,
colorValues = this . _colorValues ,
i ,
dx ,
dy ,
d ;
ctx . lineWidth = this . width ;
ctx . strokeStyle =
"rgba(" +
colorValues [ 0 ] +
"," +
colorValues [ 1 ] +
"," +
colorValues [ 2 ] +
"," +
0.5 * this . opacity +
")" ;
ctx . beginPath ( ) ;
ctx . moveTo ( lastPoint . x , lastPoint . y ) ;
ctx . lineTo ( pointer . x , pointer . y ) ;
ctx . stroke ( ) ;
ctx . strokeStyle =
"rgba(" +
colorValues [ 0 ] +
"," +
colorValues [ 1 ] +
"," +
colorValues [ 2 ] +
"," +
0.1 * this . opacity +
")" ;
for ( i = 0 ; i < points . length ; i ++ ) {
dx = points [ i ] . x - points [ this . _count ] . x ;
dy = points [ i ] . y - points [ this . _count ] . y ;
d = dx * dx + dy * dy ;
if ( d < 2500 && Math . random ( ) > 0.9 ) {
ctx . beginPath ( ) ;
ctx . moveTo ( points [ this . _count ] . x , points [ this . _count ] . y ) ;
ctx . lineTo ( points [ i ] . x , points [ i ] . y ) ;
ctx . stroke ( ) ;
}
}
this . _count ++ ;
} ,
onMouseUp : function ( pointer ) {
if ( this . _count > 0 ) {
this . convertToImg ( ) ;
}
} ,
_render : function ( ) { } ,
} ) ; // End WebBrush
2025-06-22 13:52:28 +08:00
} ) ( typeof fabric !== "undefined" ? fabric : require ( "fabric" ) . fabric ) ;