2014-05-04 13:32:02 +00:00
2018-07-27 18:04:40 +00:00
function get _brush _canvas _size ( brush _size , brush _shape ) {
// brush_shape optional, only matters if it's circle
// TODO: does it actually still matter? the ellipse drawing code has changed
// round to nearest even number in order for the canvas to be drawn centered at a point reasonably
return Math . ceil ( brush _size * ( brush _shape === "circle" ? 2.1 : 1 ) / 2 ) * 2 ;
}
2014-12-11 21:36:48 +00:00
function render _brush ( ctx , shape , size ) {
2018-06-29 03:46:07 +00:00
// USAGE NOTE: must be called outside of any other usage of op_canvas (because of draw_ellipse)
2018-06-29 00:18:47 +00:00
if ( shape . match ( /diagonal/ ) ) {
2014-12-11 21:36:48 +00:00
size -= 0.4 ;
}
2019-10-29 20:29:38 +00:00
const mid _x = Math . round ( ctx . canvas . width / 2 ) ;
const left = Math . round ( mid _x - size / 2 ) ;
const right = Math . round ( mid _x + size / 2 ) ;
const mid _y = Math . round ( ctx . canvas . height / 2 ) ;
const top = Math . round ( mid _y - size / 2 ) ;
const bottom = Math . round ( mid _y + size / 2 ) ;
2014-12-11 21:36:48 +00:00
if ( shape === "circle" ) {
2018-06-29 00:18:47 +00:00
// TODO: ideally _without_pattern_support
draw _ellipse ( ctx , left , top , size , size , false , true ) ;
2018-07-27 18:04:40 +00:00
// was useful for testing:
// ctx.fillStyle = "red";
// ctx.fillRect(mid_x, mid_y, 1, 1);
2014-12-11 21:36:48 +00:00
} else if ( shape === "square" ) {
ctx . fillRect ( left , top , ~ ~ size , ~ ~ size ) ;
} else if ( shape === "diagonal" ) {
2018-06-27 21:11:36 +00:00
draw _line _without _pattern _support ( ctx , left , top , right , bottom ) ;
2014-12-11 21:36:48 +00:00
} else if ( shape === "reverse_diagonal" ) {
2018-06-27 21:11:36 +00:00
draw _line _without _pattern _support ( ctx , left , bottom , right , top ) ;
2014-12-11 21:36:48 +00:00
} else if ( shape === "horizontal" ) {
2018-06-27 21:11:36 +00:00
draw _line _without _pattern _support ( ctx , left , mid _y , size , mid _y ) ;
2014-12-11 21:36:48 +00:00
} else if ( shape === "vertical" ) {
2018-06-27 21:11:36 +00:00
draw _line _without _pattern _support ( ctx , mid _x , top , mid _x , size ) ;
2014-12-11 21:36:48 +00:00
}
2016-11-05 19:52:44 +00:00
}
2014-12-11 21:36:48 +00:00
2014-08-10 12:36:39 +00:00
function draw _ellipse ( ctx , x , y , w , h , stroke , fill ) {
2019-10-29 20:29:38 +00:00
const center _x = x + w / 2 ;
const center _y = y + h / 2 ;
2014-05-04 13:32:02 +00:00
if ( aliasing ) {
2019-10-29 20:29:38 +00:00
const points = [ ] ;
const step = 0.05 ;
for ( let theta = 0 ; theta < TAU ; theta += step ) {
2018-06-29 00:18:47 +00:00
points . push ( {
2018-06-29 01:00:51 +00:00
x : center _x + Math . cos ( theta ) * w / 2 ,
y : center _y + Math . sin ( theta ) * h / 2 ,
2018-06-29 00:18:47 +00:00
} ) ;
2014-05-04 13:32:02 +00:00
}
2018-06-29 00:18:47 +00:00
draw _polygon ( ctx , points , stroke , fill ) ;
2014-05-04 13:32:02 +00:00
} else {
2014-10-29 02:54:55 +00:00
if ( w < 0 ) { x += w ; w = - w ; }
if ( h < 0 ) { y += h ; h = - h ; }
2014-05-04 13:32:02 +00:00
ctx . beginPath ( ) ;
2018-06-29 01:00:51 +00:00
ctx . ellipse ( center _x , center _y , w / 2 , h / 2 , 0 , TAU , false ) ;
2014-05-04 13:32:02 +00:00
ctx . stroke ( ) ;
ctx . fill ( ) ;
}
}
2014-10-29 02:54:55 +00:00
2018-06-29 05:01:55 +00:00
function draw _rounded _rectangle ( ctx , x , y , width , height , radius _x , radius _y , stroke , fill ) {
2014-10-29 02:54:55 +00:00
2014-05-04 13:32:02 +00:00
if ( aliasing ) {
2019-10-29 20:29:38 +00:00
const points = [ ] ;
const lineTo = ( x , y ) => {
2018-06-28 23:14:32 +00:00
points . push ( { x , y } ) ;
} ;
2019-10-29 20:29:38 +00:00
const arc = ( x , y , radius _x , radius _y , startAngle , endAngle ) => {
const step = 0.05 ;
for ( let theta = startAngle ; theta < endAngle ; theta += step ) {
2018-06-28 23:14:32 +00:00
points . push ( {
2018-06-29 05:01:55 +00:00
x : x + Math . cos ( theta ) * radius _x ,
y : y + Math . sin ( theta ) * radius _y ,
2018-06-28 23:14:32 +00:00
} ) ;
}
// not just doing `theta <= endAngle` above because that doesn't account for floating point rounding errors
points . push ( {
2018-06-29 05:01:55 +00:00
x : x + Math . cos ( endAngle ) * radius _x ,
y : y + Math . sin ( endAngle ) * radius _y ,
2018-06-28 23:14:32 +00:00
} ) ;
} ;
2018-06-29 01:00:51 +00:00
2019-10-29 20:29:38 +00:00
const x2 = x + width ;
const y2 = y + height ;
2018-06-29 05:01:55 +00:00
arc ( x2 - radius _x , y + radius _y , radius _x , radius _y , TAU * 3 / 4 , TAU , false ) ;
lineTo ( x2 , y2 - radius _y ) ;
arc ( x2 - radius _x , y2 - radius _y , radius _x , radius _y , 0 , TAU * 1 / 4 , false ) ;
lineTo ( x + radius _x , y2 ) ;
arc ( x + radius _x , y2 - radius _y , radius _x , radius _y , TAU * 1 / 4 , TAU * 1 / 2 , false ) ;
lineTo ( x , y + radius _y ) ;
arc ( x + radius _x , y + radius _y , radius _x , radius _y , TAU / 2 , TAU * 3 / 4 , false ) ;
2018-06-28 23:14:32 +00:00
draw _polygon ( ctx , points , stroke , fill ) ;
2014-05-04 13:32:02 +00:00
} else {
ctx . beginPath ( ) ;
2018-06-29 05:01:55 +00:00
ctx . moveTo ( x + radius _x , y ) ;
ctx . lineTo ( x + width - radius _x , y ) ;
ctx . quadraticCurveTo ( x + width , y , x + width , y + radius _y ) ;
ctx . lineTo ( x + width , y + height - radius _y ) ;
ctx . quadraticCurveTo ( x + width , y + height , x + width - radius _x , y + height ) ;
ctx . lineTo ( x + radius _x , y + height ) ;
ctx . quadraticCurveTo ( x , y + height , x , y + height - radius _y ) ;
ctx . lineTo ( x , y + radius _y ) ;
ctx . quadraticCurveTo ( x , y , x + radius _x , y ) ;
2014-05-04 13:32:02 +00:00
ctx . closePath ( ) ;
2018-06-28 23:14:32 +00:00
if ( stroke ) {
ctx . stroke ( ) ;
}
if ( fill ) {
ctx . fill ( ) ;
}
2014-05-04 13:32:02 +00:00
}
}
2014-10-29 02:54:55 +00:00
2019-12-11 20:42:22 +00:00
// USAGE NOTE: must be called outside of any other usage of op_canvas (because of render_brush)
2019-12-11 23:03:29 +00:00
// TODO: protect against browser clearing canvases, invalidate cache
const get _brush _canvas = memoize _synchronous _function _with _limit ( ( brush _shape , brush _size ) => {
const canvas _size = get _brush _canvas _size ( brush _size , brush _shape ) ;
const brush _canvas = make _canvas ( canvas _size , canvas _size ) ;
2019-12-11 20:42:22 +00:00
2019-12-11 23:03:29 +00:00
// brush_canvas.ctx.fillStyle = brush_canvas.ctx.strokeStyle = "black";
2019-12-11 20:42:22 +00:00
render _brush ( brush _canvas . ctx , brush _shape , brush _size ) ;
2019-12-11 23:03:29 +00:00
return brush _canvas ;
} , 10 ) ;
// USAGE NOTE: must be called outside of any other usage of op_canvas (because of render_brush)
const stamp _brush _canvas = ( ctx , x , y , brush _shape , brush _size ) => {
const brush _canvas = get _brush _canvas ( brush _shape , brush _size ) ;
const offset _x = - Math . ceil ( brush _canvas . width / 2 ) ;
const offset _y = - Math . ceil ( brush _canvas . height / 2 ) ;
ctx . drawImage ( brush _canvas , x + offset _x , y + offset _y ) ;
} ;
// USAGE NOTE: must be called outside of any other usage of op_canvas (because of render_brush)
const get _circumference _points _for _brush = memoize _synchronous _function ( ( brush _shape , brush _size ) => {
const brush _canvas = get _brush _canvas ( brush _shape , brush _size ) ;
2019-12-11 20:42:22 +00:00
const image _data = brush _canvas . ctx . getImageData ( 0 , 0 , brush _canvas . width , brush _canvas . height ) ;
const at = ( x , y ) => image _data . data [ ( y * image _data . width + x ) * 4 + 3 ] > 0 ;
2019-12-11 23:03:29 +00:00
const offset _x = - Math . ceil ( brush _canvas . width / 2 ) ;
const offset _y = - Math . ceil ( brush _canvas . height / 2 ) ;
2019-12-11 20:42:22 +00:00
const points = [ ] ;
for ( let x = 0 ; x < image _data . width ; x += 1 ) {
for ( let y = 0 ; y < image _data . height ; y += 1 ) {
if ( at ( x , y ) && (
! at ( x , y - 1 ) ||
! at ( x , y + 1 ) ||
! at ( x - 1 , y ) ||
! at ( x + 1 , y )
) ) {
points . push ( {
x : x + offset _x ,
y : y + offset _y ,
} ) ;
}
}
}
return points ;
} ) ;
2019-10-29 20:29:38 +00:00
let line _brush _canvas ;
2019-12-12 02:57:02 +00:00
// USAGE NOTE: must be called outside of any other usage of op_canvas (because of render_brush)
2018-06-18 04:52:33 +00:00
function update _brush _for _drawing _lines ( stroke _size ) {
2019-12-12 02:57:02 +00:00
if ( aliasing && stroke _size > 1 ) {
line _brush _canvas = get _brush _canvas ( "circle" , stroke _size ) ;
}
2018-06-18 04:52:33 +00:00
}
2019-10-29 22:15:45 +00:00
function draw _line _without _pattern _support ( ctx , x1 , y1 , x2 , y2 , stroke _size = 1 ) {
2014-05-04 13:32:02 +00:00
if ( aliasing ) {
2016-11-06 00:13:54 +00:00
if ( stroke _size > 1 ) {
2019-10-29 18:46:29 +00:00
bresenham _line ( x1 , y1 , x2 , y2 , ( x , y ) => {
2018-06-18 04:52:33 +00:00
ctx . drawImage ( line _brush _canvas , ~ ~ ( x - line _brush _canvas . width / 2 ) , ~ ~ ( y - line _brush _canvas . height / 2 ) ) ;
2016-11-06 00:13:54 +00:00
} ) ;
} else {
2019-10-29 18:46:29 +00:00
bresenham _line ( x1 , y1 , x2 , y2 , ( x , y ) => {
2016-11-06 00:13:54 +00:00
ctx . fillRect ( x , y , 1 , 1 ) ;
} ) ;
}
2014-05-04 13:32:02 +00:00
} else {
ctx . beginPath ( ) ;
ctx . moveTo ( x1 , y1 ) ;
ctx . lineTo ( x2 , y2 ) ;
2014-08-10 15:09:56 +00:00
2016-11-06 00:13:54 +00:00
ctx . lineWidth = stroke _size ;
2014-08-10 15:09:56 +00:00
ctx . lineCap = "round" ;
2014-05-04 13:32:02 +00:00
ctx . stroke ( ) ;
2014-08-08 21:44:46 +00:00
ctx . lineCap = "butt" ;
2014-05-04 13:32:02 +00:00
}
}
2014-10-29 02:54:55 +00:00
2014-08-11 20:45:55 +00:00
function bresenham _line ( x1 , y1 , x2 , y2 , callback ) {
2014-05-04 13:32:02 +00:00
// Bresenham's line algorithm
x1 = ~ ~ x1 , x2 = ~ ~ x2 , y1 = ~ ~ y1 , y2 = ~ ~ y2 ;
2019-10-29 20:29:38 +00:00
const dx = Math . abs ( x2 - x1 ) ;
const dy = Math . abs ( y2 - y1 ) ;
const sx = ( x1 < x2 ) ? 1 : - 1 ;
const sy = ( y1 < y2 ) ? 1 : - 1 ;
let err = dx - dy ;
2014-05-04 13:32:02 +00:00
2019-09-21 15:33:01 +00:00
// eslint-disable-next-line no-constant-condition
while ( true ) {
2014-05-04 13:32:02 +00:00
callback ( x1 , y1 ) ;
if ( x1 === x2 && y1 === y2 ) break ;
2019-10-29 20:29:38 +00:00
const e2 = err * 2 ;
2014-05-04 13:32:02 +00:00
if ( e2 > - dy ) { err -= dy ; x1 += sx ; }
if ( e2 < dx ) { err += dx ; y1 += sy ; }
}
}
2014-10-29 02:54:55 +00:00
2014-08-11 20:45:55 +00:00
function brosandham _line ( x1 , y1 , x2 , y2 , callback ) {
2016-11-05 19:52:44 +00:00
// Bresenham's line argorithm with a callback between going horizontal and vertical
2014-05-15 17:14:23 +00:00
x1 = ~ ~ x1 , x2 = ~ ~ x2 , y1 = ~ ~ y1 , y2 = ~ ~ y2 ;
2019-10-29 20:29:38 +00:00
const dx = Math . abs ( x2 - x1 ) ;
const dy = Math . abs ( y2 - y1 ) ;
const sx = ( x1 < x2 ) ? 1 : - 1 ;
const sy = ( y1 < y2 ) ? 1 : - 1 ;
let err = dx - dy ;
2014-05-15 17:14:23 +00:00
2019-09-21 15:33:01 +00:00
// eslint-disable-next-line no-constant-condition
while ( true ) {
2014-05-15 17:14:23 +00:00
callback ( x1 , y1 ) ;
if ( x1 === x2 && y1 === y2 ) break ;
2019-10-29 20:29:38 +00:00
const e2 = err * 2 ;
2014-05-15 17:14:23 +00:00
if ( e2 > - dy ) { err -= dy ; x1 += sx ; }
callback ( x1 , y1 ) ;
if ( e2 < dx ) { err += dx ; y1 += sy ; }
}
}
2014-10-29 02:54:55 +00:00
2019-09-21 15:59:30 +00:00
function draw _fill ( ctx , start _x , start _y , fill _r , fill _g , fill _b , fill _a ) {
2014-10-01 19:46:35 +00:00
2018-01-18 03:18:52 +00:00
// TODO: split up processing in case it takes too long?
// progress bar and abort button (outside of image-manipulation.js)
// or at least just free up the main thread every once in a while
// TODO: speed up with typed arrays? https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipulation-with-typed-arrays/
// could avoid endianness issues if only copying colors
// the jsperf only shows ~15% improvement
// maybe do something fancier like special-casing large chunks of single-color image
// (octree? or just have a higher level stack of chunks to fill and check at if a chunk is homogeneous)
2019-10-29 20:29:38 +00:00
const stack = [ [ start _x , start _y ] ] ;
const c _width = canvas . width ;
const c _height = canvas . height ;
const id = ctx . getImageData ( 0 , 0 , c _width , c _height ) ;
2019-11-03 15:36:34 +00:00
let pixel _pos = ( start _y * c _width + start _x ) * 4 ;
2019-10-29 20:29:38 +00:00
const start _r = id . data [ pixel _pos + 0 ] ;
const start _g = id . data [ pixel _pos + 1 ] ;
const start _b = id . data [ pixel _pos + 2 ] ;
const start _a = id . data [ pixel _pos + 3 ] ;
2014-10-01 19:46:35 +00:00
if (
fill _r === start _r &&
fill _g === start _g &&
fill _b === start _b &&
fill _a === start _a
) {
return ;
}
while ( stack . length ) {
2019-10-29 21:00:31 +00:00
let new _pos ;
let x ;
let y ;
let reach _left ;
let reach _right ;
new _pos = stack . pop ( ) ;
x = new _pos [ 0 ] ;
y = new _pos [ 1 ] ;
pixel _pos = ( y * c _width + x ) * 4 ;
while ( matches _start _color ( pixel _pos ) ) {
2017-05-25 03:07:10 +00:00
y -- ;
pixel _pos = ( y * c _width + x ) * 4 ;
2014-10-01 19:46:35 +00:00
}
2019-10-29 21:00:31 +00:00
reach _left = false ;
reach _right = false ;
// eslint-disable-next-line no-constant-condition
while ( true ) {
2017-05-25 03:07:10 +00:00
y ++ ;
pixel _pos = ( y * c _width + x ) * 4 ;
if ( ! ( y < c _height && matches _start _color ( pixel _pos ) ) ) {
break ;
}
2014-10-01 19:46:35 +00:00
color _pixel ( pixel _pos ) ;
if ( x > 0 ) {
2017-05-25 03:07:10 +00:00
if ( matches _start _color ( pixel _pos - 4 ) ) {
2014-10-01 19:46:35 +00:00
if ( ! reach _left ) {
stack . push ( [ x - 1 , y ] ) ;
reach _left = true ;
}
} else if ( reach _left ) {
reach _left = false ;
}
}
if ( x < c _width - 1 ) {
2017-05-25 03:07:10 +00:00
if ( matches _start _color ( pixel _pos + 4 ) ) {
2014-10-01 19:46:35 +00:00
if ( ! reach _right ) {
stack . push ( [ x + 1 , y ] ) ;
reach _right = true ;
}
} else if ( reach _right ) {
reach _right = false ;
}
}
pixel _pos += c _width * 4 ;
}
2019-10-29 21:00:31 +00:00
}
2014-10-01 19:46:35 +00:00
ctx . putImageData ( id , 0 , 0 ) ;
2017-05-25 03:07:10 +00:00
function matches _start _color ( pixel _pos ) {
2014-10-01 19:46:35 +00:00
return (
id . data [ pixel _pos + 0 ] === start _r &&
id . data [ pixel _pos + 1 ] === start _g &&
id . data [ pixel _pos + 2 ] === start _b &&
id . data [ pixel _pos + 3 ] === start _a
) ;
}
2018-01-25 03:51:12 +00:00
function color _pixel ( pixel _pos ) {
id . data [ pixel _pos + 0 ] = fill _r ;
id . data [ pixel _pos + 1 ] = fill _g ;
id . data [ pixel _pos + 2 ] = fill _b ;
id . data [ pixel _pos + 3 ] = fill _a ;
}
}
function draw _noncontiguous _fill ( ctx , x , y , fill _r , fill _g , fill _b , fill _a ) {
2019-10-29 20:29:38 +00:00
const c _width = canvas . width ;
const c _height = canvas . height ;
const id = ctx . getImageData ( 0 , 0 , c _width , c _height ) ;
2018-01-25 03:51:12 +00:00
pixel _pos = ( y * c _width + x ) * 4 ;
2019-10-29 20:29:38 +00:00
const start _r = id . data [ pixel _pos + 0 ] ;
const start _g = id . data [ pixel _pos + 1 ] ;
const start _b = id . data [ pixel _pos + 2 ] ;
const start _a = id . data [ pixel _pos + 3 ] ;
2018-01-25 03:51:12 +00:00
if (
fill _r === start _r &&
fill _g === start _g &&
fill _b === start _b &&
fill _a === start _a
) {
return ;
}
2019-10-29 20:29:38 +00:00
for ( let i = 0 ; i < id . data . length ; i += 4 ) {
2018-01-25 03:51:12 +00:00
if ( matches _start _color ( i ) ) {
color _pixel ( i ) ;
}
}
ctx . putImageData ( id , 0 , 0 ) ;
function matches _start _color ( pixel _pos ) {
return (
id . data [ pixel _pos + 0 ] === start _r &&
id . data [ pixel _pos + 1 ] === start _g &&
id . data [ pixel _pos + 2 ] === start _b &&
id . data [ pixel _pos + 3 ] === start _a
) ;
}
2014-10-01 19:46:35 +00:00
function color _pixel ( pixel _pos ) {
id . data [ pixel _pos + 0 ] = fill _r ;
id . data [ pixel _pos + 1 ] = fill _g ;
id . data [ pixel _pos + 2 ] = fill _b ;
id . data [ pixel _pos + 3 ] = fill _a ;
}
}
2014-10-02 21:17:43 +00:00
2019-12-15 04:11:30 +00:00
function apply _image _transformation ( meta , fn ) {
2014-10-02 21:17:43 +00:00
// Apply an image transformation function to either the selection or the entire canvas
2019-10-29 20:29:38 +00:00
const original _canvas = selection ? selection . source _canvas : canvas ;
2014-10-02 21:17:43 +00:00
2019-10-29 21:43:46 +00:00
const new _canvas = make _canvas ( original _canvas . width , original _canvas . height ) ;
2018-01-18 01:13:02 +00:00
2019-10-29 20:29:38 +00:00
const original _ctx = original _canvas . getContext ( "2d" ) ;
const new _ctx = new _canvas . getContext ( "2d" ) ;
2018-01-18 01:13:02 +00:00
2014-10-02 21:17:43 +00:00
fn ( original _canvas , original _ctx , new _canvas , new _ctx ) ;
if ( selection ) {
2019-12-15 02:32:57 +00:00
undoable ( {
2019-12-15 04:11:30 +00:00
name : ` ${ meta . name } Selection ` ,
icon : meta . icon ,
2019-12-15 02:32:57 +00:00
soft : true ,
} , ( ) => {
2019-12-13 03:48:32 +00:00
selection . replace _source _canvas ( new _canvas ) ;
} ) ;
2014-10-02 21:17:43 +00:00
} else {
2019-12-16 01:54:03 +00:00
deselect ( ) ;
cancel ( ) ;
2019-12-15 04:11:30 +00:00
undoable ( {
name : meta . name ,
icon : meta . icon ,
} , ( ) => {
2019-12-08 14:07:31 +00:00
saved = false ;
2014-10-02 21:17:43 +00:00
2014-12-07 22:19:56 +00:00
ctx . copy ( new _canvas ) ;
2014-10-02 21:17:43 +00:00
$canvas . trigger ( "update" ) ; // update handles
} ) ;
}
}
2014-12-08 02:45:23 +00:00
function flip _horizontal ( ) {
2019-12-15 05:05:11 +00:00
apply _image _transformation ( {
name : "Flip Horizontal" ,
icon : get _help _folder _icon ( "p_fliph.png" ) ,
} , ( original _canvas , original _ctx , new _canvas , new _ctx ) => {
2014-12-08 02:45:23 +00:00
new _ctx . translate ( new _canvas . width , 0 ) ;
new _ctx . scale ( - 1 , 1 ) ;
new _ctx . drawImage ( original _canvas , 0 , 0 ) ;
} ) ;
}
function flip _vertical ( ) {
2019-12-15 05:05:11 +00:00
apply _image _transformation ( {
name : "Flip Vertical" ,
icon : get _help _folder _icon ( "p_flipv.png" ) ,
} , ( original _canvas , original _ctx , new _canvas , new _ctx ) => {
2014-12-08 02:45:23 +00:00
new _ctx . translate ( 0 , new _canvas . height ) ;
new _ctx . scale ( 1 , - 1 ) ;
new _ctx . drawImage ( original _canvas , 0 , 0 ) ;
} ) ;
}
function rotate ( angle ) {
2019-12-15 05:05:11 +00:00
apply _image _transformation ( {
name : ` Rotate ${ angle / TAU * 360 } degrees ` ,
icon : get _help _folder _icon ( ` p_rotate_ ${ angle >= 0 ? "cw" : "ccw" } .png ` ) ,
} , ( original _canvas , original _ctx , new _canvas , new _ctx ) => {
2014-12-08 13:56:20 +00:00
new _ctx . save ( ) ;
2014-12-08 02:45:23 +00:00
switch ( angle ) {
case TAU / 4 :
case TAU * - 3 / 4 :
new _canvas . width = original _canvas . height ;
new _canvas . height = original _canvas . width ;
2018-01-21 21:02:45 +00:00
new _ctx . disable _image _smoothing ( ) ;
2014-12-08 02:45:23 +00:00
new _ctx . translate ( new _canvas . width , 0 ) ;
new _ctx . rotate ( TAU / 4 ) ;
break ;
case TAU / 2 :
case TAU / - 2 :
new _ctx . translate ( new _canvas . width , new _canvas . height ) ;
new _ctx . rotate ( TAU / 2 ) ;
break ;
case TAU * 3 / 4 :
case TAU / - 4 :
new _canvas . width = original _canvas . height ;
new _canvas . height = original _canvas . width ;
2018-01-21 21:02:45 +00:00
new _ctx . disable _image _smoothing ( ) ;
2014-12-08 02:45:23 +00:00
new _ctx . translate ( 0 , new _canvas . height ) ;
new _ctx . rotate ( TAU / - 4 ) ;
break ;
2019-10-29 21:08:13 +00:00
default : {
2019-10-29 20:29:38 +00:00
const w = original _canvas . width ;
const h = original _canvas . height ;
2014-12-08 13:56:20 +00:00
2019-10-29 20:29:38 +00:00
let bb _min _x = + Infinity ;
let bb _max _x = - Infinity ;
let bb _min _y = + Infinity ;
let bb _max _y = - Infinity ;
const corner = ( x01 , y01 ) => {
const x = Math . sin ( - angle ) * h * x01 + Math . cos ( + angle ) * w * y01 ;
const y = Math . sin ( + angle ) * w * y01 + Math . cos ( - angle ) * h * x01 ;
2014-12-08 13:56:20 +00:00
bb _min _x = Math . min ( bb _min _x , x ) ;
bb _max _x = Math . max ( bb _max _x , x ) ;
bb _min _y = Math . min ( bb _min _y , y ) ;
bb _max _y = Math . max ( bb _max _y , y ) ;
} ;
corner ( 0 , 0 ) ;
corner ( 0 , 1 ) ;
corner ( 1 , 0 ) ;
corner ( 1 , 1 ) ;
2019-10-29 20:29:38 +00:00
const bb _x = bb _min _x ;
const bb _y = bb _min _y ;
const bb _w = bb _max _x - bb _min _x ;
const bb _h = bb _max _y - bb _min _y ;
2014-12-08 13:56:20 +00:00
new _canvas . width = bb _w ;
new _canvas . height = bb _h ;
2018-01-21 21:02:45 +00:00
new _ctx . disable _image _smoothing ( ) ;
2014-12-08 13:56:20 +00:00
if ( ! transparency ) {
2015-02-24 00:18:07 +00:00
new _ctx . fillStyle = colors . background ;
2014-12-08 13:56:20 +00:00
new _ctx . fillRect ( 0 , 0 , new _canvas . width , new _canvas . height ) ;
}
new _ctx . translate ( - bb _x , - bb _y ) ;
new _ctx . rotate ( angle ) ;
new _ctx . drawImage ( original _canvas , 0 , 0 , w , h ) ;
2014-12-08 02:45:23 +00:00
break ;
2019-10-29 21:08:13 +00:00
}
2014-12-08 02:45:23 +00:00
}
new _ctx . drawImage ( original _canvas , 0 , 0 ) ;
2014-12-08 13:56:20 +00:00
new _ctx . restore ( ) ;
2014-12-08 02:45:23 +00:00
} ) ;
}
2014-11-29 19:20:59 +00:00
function stretch _and _skew ( xscale , yscale , hsa , vsa ) {
2019-12-15 18:01:47 +00:00
apply _image _transformation ( {
2019-12-15 18:15:31 +00:00
name :
2019-12-15 18:01:47 +00:00
( hsa !== 0 || vsa !== 0 ) ? (
( xscale !== 1 || yscale !== 1 ) ? "Stretch/Skew" : "Skew"
2019-12-15 18:15:31 +00:00
) : "Stretch" ,
2019-12-15 18:01:47 +00:00
icon : get _help _folder _icon (
( hsa !== 0 ) ? "p_skew_h.png" :
( vsa !== 0 ) ? "p_skew_v.png" :
2019-12-15 18:15:31 +00:00
( yscale !== 1 ) ? (
( xscale !== 1 ) ? "p_stretch_both.png" : "p_stretch_v.png"
) : "p_stretch_h.png"
2019-12-15 18:01:47 +00:00
) ,
} , ( original _canvas , original _ctx , new _canvas , new _ctx ) => {
2019-10-29 20:29:38 +00:00
const w = original _canvas . width * xscale ;
const h = original _canvas . height * yscale ;
2014-11-29 19:20:59 +00:00
2019-10-29 20:29:38 +00:00
let bb _min _x = + Infinity ;
let bb _max _x = - Infinity ;
let bb _min _y = + Infinity ;
let bb _max _y = - Infinity ;
const corner = ( x01 , y01 ) => {
const x = Math . tan ( hsa ) * h * x01 + w * y01 ;
const y = Math . tan ( vsa ) * w * y01 + h * x01 ;
2014-11-29 19:20:59 +00:00
bb _min _x = Math . min ( bb _min _x , x ) ;
bb _max _x = Math . max ( bb _max _x , x ) ;
bb _min _y = Math . min ( bb _min _y , y ) ;
bb _max _y = Math . max ( bb _max _y , y ) ;
} ;
corner ( 0 , 0 ) ;
corner ( 0 , 1 ) ;
corner ( 1 , 0 ) ;
corner ( 1 , 1 ) ;
2019-10-29 20:29:38 +00:00
const bb _x = bb _min _x ;
const bb _y = bb _min _y ;
const bb _w = bb _max _x - bb _min _x ;
const bb _h = bb _max _y - bb _min _y ;
2014-11-29 19:20:59 +00:00
2019-12-15 18:23:10 +00:00
new _canvas . width = Math . max ( 1 , bb _w ) ;
new _canvas . height = Math . max ( 1 , bb _h ) ;
2018-01-21 21:02:45 +00:00
new _ctx . disable _image _smoothing ( ) ;
2014-11-29 19:20:59 +00:00
if ( ! transparency ) {
2015-02-24 00:18:07 +00:00
new _ctx . fillStyle = colors . background ;
2014-11-29 19:20:59 +00:00
new _ctx . fillRect ( 0 , 0 , new _canvas . width , new _canvas . height ) ;
}
new _ctx . save ( ) ;
new _ctx . transform (
1 , // x scale
Math . tan ( vsa ) , // vertical skew (skewY)
Math . tan ( hsa ) , // horizontal skew (skewX)
1 , // y scale
2014-12-08 13:14:58 +00:00
- bb _x , // x translation
- bb _y // y translation
2014-11-29 19:20:59 +00:00
) ;
new _ctx . drawImage ( original _canvas , 0 , 0 , w , h ) ;
new _ctx . restore ( ) ;
} ) ;
}
2019-12-15 03:32:34 +00:00
function invert _rgb ( source _ctx , dest _ctx = source _ctx ) {
const image _data = source _ctx . getImageData ( 0 , 0 , source _ctx . canvas . width , source _ctx . canvas . height ) ;
for ( let i = 0 ; i < image _data . data . length ; i += 4 ) {
image _data . data [ i + 0 ] = 255 - image _data . data [ i + 0 ] ;
image _data . data [ i + 1 ] = 255 - image _data . data [ i + 1 ] ;
image _data . data [ i + 2 ] = 255 - image _data . data [ i + 2 ] ;
}
dest _ctx . putImageData ( image _data , 0 , 0 ) ;
}
2018-06-17 23:01:04 +00:00
function replace _colors _with _swatch ( ctx , swatch , x _offset _from _global _canvas , y _offset _from _global _canvas ) {
2018-06-28 23:14:32 +00:00
// USAGE NOTE: Context MUST be untranslated! (for the rectangle to cover the exact area of the canvas, and presumably for the pattern alignment as well)
2018-06-27 21:11:36 +00:00
// This function is mainly for patterns support (for black & white mode) but naturally handles solid colors as well.
2018-06-17 23:01:04 +00:00
ctx . globalCompositeOperation = "source-in" ;
ctx . fillStyle = swatch ;
ctx . beginPath ( ) ;
ctx . rect ( 0 , 0 , ctx . canvas . width , ctx . canvas . height ) ;
ctx . save ( ) ;
2018-06-17 23:25:47 +00:00
ctx . translate ( - x _offset _from _global _canvas , - y _offset _from _global _canvas ) ;
2018-06-17 23:01:04 +00:00
ctx . fill ( ) ;
ctx . restore ( ) ;
}
2018-06-18 04:05:44 +00:00
// adapted from https://github.com/Pomax/bezierjs
function compute _bezier ( t , start _x , start _y , control _1 _x , control _1 _y , control _2 _x , control _2 _y , end _x , end _y ) {
2019-10-29 20:29:38 +00:00
const mt = 1 - t ;
const mt2 = mt * mt ;
const t2 = t * t ;
let a , b , c , d = 0 ;
2018-06-29 01:00:51 +00:00
2018-06-18 04:05:44 +00:00
a = mt2 * mt ;
b = mt2 * t * 3 ;
c = mt * t2 * 3 ;
d = t * t2 ;
2018-06-29 01:00:51 +00:00
2018-06-18 04:05:44 +00:00
return {
2018-06-29 01:00:51 +00:00
x : a * start _x + b * control _1 _x + c * control _2 _x + d * end _x ,
y : a * start _y + b * control _1 _y + c * control _2 _y + d * end _y
2018-06-18 04:05:44 +00:00
} ;
}
2018-06-27 21:11:36 +00:00
function draw _bezier _curve _without _pattern _support ( ctx , start _x , start _y , control _1 _x , control _1 _y , control _2 _x , control _2 _y , end _x , end _y , stroke _size ) {
2019-10-29 20:29:38 +00:00
const steps = 100 ;
let point _a = { x : start _x , y : start _y } ;
for ( let t = 0 ; t < 1 ; t += 1 / steps ) {
const point _b = compute _bezier ( t , start _x , start _y , control _1 _x , control _1 _y , control _2 _x , control _2 _y , end _x , end _y ) ;
2018-06-27 21:11:36 +00:00
// TODO: carry "error" from Bresenham line algorithm between iterations? and/or get a proper Bezier drawing algorithm
draw _line _without _pattern _support ( ctx , point _a . x , point _a . y , point _b . x , point _b . y , stroke _size ) ;
2018-06-18 04:05:44 +00:00
point _a = point _b ;
}
2019-09-21 15:22:35 +00:00
}
2018-06-18 04:05:44 +00:00
function draw _quadratic _curve ( ctx , start _x , start _y , control _x , control _y , end _x , end _y , stroke _size ) {
draw _bezier _curve ( ctx , start _x , start _y , control _x , control _y , control _x , control _y , end _x , end _y , stroke _size ) ;
2019-09-21 15:22:35 +00:00
}
2018-06-18 04:05:44 +00:00
2018-06-27 21:11:36 +00:00
function draw _bezier _curve ( ctx , start _x , start _y , control _1 _x , control _1 _y , control _2 _x , control _2 _y , end _x , end _y , stroke _size ) {
// could calculate bounds of Bezier curve with something like bezier-js
// but just using the control points should be fine
2019-10-29 20:29:38 +00:00
const min _x = Math . min ( start _x , control _1 _x , control _2 _x , end _x ) ;
const min _y = Math . min ( start _y , control _1 _y , control _2 _y , end _y ) ;
const max _x = Math . max ( start _x , control _1 _x , control _2 _x , end _x ) ;
const max _y = Math . max ( start _y , control _1 _y , control _2 _y , end _y ) ;
2019-10-29 18:46:29 +00:00
draw _with _swatch ( ctx , min _x , min _y , max _x , max _y , stroke _color , op _ctx _2d => {
2018-06-27 21:11:36 +00:00
draw _bezier _curve _without _pattern _support ( op _ctx _2d , start _x , start _y , control _1 _x , control _1 _y , control _2 _x , control _2 _y , end _x , end _y , stroke _size ) ;
} ) ;
}
function draw _line ( ctx , x1 , y1 , x2 , y2 , stroke _size ) {
2019-10-29 20:29:38 +00:00
const min _x = Math . min ( x1 , x2 ) ;
const min _y = Math . min ( y1 , y2 ) ;
const max _x = Math . max ( x1 , x2 ) ;
const max _y = Math . max ( y1 , y2 ) ;
2019-10-29 18:46:29 +00:00
draw _with _swatch ( ctx , min _x , min _y , max _x , max _y , stroke _color , op _ctx _2d => {
2018-06-27 21:11:36 +00:00
draw _line _without _pattern _support ( op _ctx _2d , x1 , y1 , x2 , y2 , stroke _size ) ;
} ) ;
// also works:
2018-06-27 22:03:02 +00:00
// draw_line_strip(ctx, [{x: x1, y: y1}, {x: x2, y: y2}]);
2018-06-27 21:11:36 +00:00
}
2019-10-29 20:29:38 +00:00
let grid _pattern ;
2019-10-28 23:40:06 +00:00
function draw _grid ( ctx , scale ) {
2019-10-29 20:29:38 +00:00
const pattern _size = Math . floor ( scale ) ; // TODO: try ceil too
2019-10-28 23:40:06 +00:00
if ( ! grid _pattern || grid _pattern . width !== pattern _size || grid _pattern . height !== pattern _size ) {
2019-10-29 21:43:46 +00:00
const grid _pattern _canvas = make _canvas ( pattern _size , pattern _size ) ;
2019-10-29 20:29:38 +00:00
const dark _gray = "#808080" ;
const light _gray = "#c0c0c0" ;
2019-09-30 14:40:47 +00:00
grid _pattern _canvas . ctx . fillStyle = dark _gray ;
2019-10-28 23:40:06 +00:00
grid _pattern _canvas . ctx . fillRect ( 0 , 0 , 1 , pattern _size ) ;
2019-09-30 14:40:47 +00:00
grid _pattern _canvas . ctx . fillStyle = dark _gray ;
2019-10-28 23:40:06 +00:00
grid _pattern _canvas . ctx . fillRect ( 0 , 0 , pattern _size , 1 ) ;
2019-09-30 14:40:47 +00:00
grid _pattern _canvas . ctx . fillStyle = light _gray ;
2019-10-28 23:40:06 +00:00
for ( let i = 1 ; i < pattern _size ; i += 2 ) {
2019-09-30 14:40:47 +00:00
grid _pattern _canvas . ctx . fillRect ( i , 0 , 1 , 1 ) ;
grid _pattern _canvas . ctx . fillRect ( 0 , i , 1 , 1 ) ;
}
grid _pattern = ctx . createPattern ( grid _pattern _canvas , "repeat" ) ;
}
2019-10-28 23:40:06 +00:00
ctx . save ( ) ;
ctx . rect ( 0 , 0 , ctx . canvas . width , ctx . canvas . height ) ;
if ( scale !== pattern _size ) {
ctx . translate ( - 0.5 , - 0.75 ) ; // hand picked to look "good" at 110% in chrome
// might be better to just hide the grid in some more cases tho
// ...TODO: if I can get helper layer to be pixel aligned, I can probably remove this
}
ctx . scale ( scale / pattern _size , scale / pattern _size ) ;
ctx . enable _image _smoothing ( ) ;
2019-09-30 14:40:47 +00:00
ctx . fillStyle = grid _pattern ;
2019-10-28 23:40:06 +00:00
ctx . fill ( ) ;
ctx . restore ( ) ;
2019-09-30 14:40:47 +00:00
}
2019-10-30 02:04:48 +00:00
( ( ) => {
// the dashes of the border are sized such that at 4x zoom,
// they're squares equal to one canvas pixel
// they're offset by a screen pixel tho from the canvas pixel cells
const svg _for _creating _matrices = document . createElementNS ( "http://www.w3.org/2000/svg" , "svg" ) ;
const horizontal _pattern _canvas = make _canvas ( 8 , 4 ) ;
const vertical _pattern _canvas = make _canvas ( 4 , 8 ) ;
let horizontal _pattern ;
let vertical _pattern ;
function draw _dashes ( ctx , x , y , go _x , go _y , scale , translate _x , translate _y ) {
if ( ! vertical _pattern ) {
horizontal _pattern _canvas . ctx . fillStyle = "white" ;
horizontal _pattern _canvas . ctx . fillRect ( 4 , 0 , 4 , 4 ) ;
vertical _pattern _canvas . ctx . fillStyle = "white" ;
vertical _pattern _canvas . ctx . fillRect ( 0 , 4 , 4 , 4 ) ;
horizontal _pattern = ctx . createPattern ( horizontal _pattern _canvas , "repeat" ) ;
vertical _pattern = ctx . createPattern ( vertical _pattern _canvas , "repeat" ) ;
}
const dash _length = 4 / magnification ;
const dash _width = 1 ;
const hairline _width = 1 / scale ; // size of a screen pixel
ctx . save ( ) ;
ctx . scale ( scale , scale ) ;
ctx . translate ( translate _x , translate _y ) ;
ctx . translate ( x , y ) ;
ctx . globalCompositeOperation = "difference" ;
if ( go _x > 0 ) {
const matrix = svg _for _creating _matrices . createSVGMatrix ( ) ;
2019-12-04 18:46:45 +00:00
if ( horizontal _pattern . setTransform ) { // not supported by Edge as of 2019-12-04
horizontal _pattern . setTransform ( matrix . translate ( - x , - y ) . translate ( hairline _width , 0 ) . scale ( 1 / scale ) ) ;
}
2019-10-30 02:04:48 +00:00
ctx . fillStyle = horizontal _pattern ;
ctx . fillRect ( 0 , 0 , go _x , dash _width ) ;
} else if ( go _y > 0 ) {
const matrix = svg _for _creating _matrices . createSVGMatrix ( ) ;
2019-12-04 18:46:45 +00:00
if ( vertical _pattern . setTransform ) { // not supported by Edge as of 2019-12-04
vertical _pattern . setTransform ( matrix . translate ( - x , - y ) . translate ( 0 , hairline _width ) . scale ( 1 / scale ) ) ;
}
2019-10-30 02:04:48 +00:00
ctx . fillStyle = vertical _pattern ;
ctx . fillRect ( 0 , 0 , dash _width , go _y ) ;
}
ctx . restore ( ) ;
}
window . draw _selection _box = ( ctx , rect _x , rect _y , rect _w , rect _h , scale , translate _x , translate _y ) => {
draw _dashes ( ctx , rect _x , rect _y , rect _w - 1 , 0 , scale , translate _x , translate _y ) ; // top
if ( rect _h === 1 ) {
draw _dashes ( ctx , rect _x , rect _y , 0 , 1 , scale , translate _x , translate _y ) ; // left
} else {
draw _dashes ( ctx , rect _x , rect _y + 1 , 0 , rect _h - 2 , scale , translate _x , translate _y ) ; // left
}
draw _dashes ( ctx , rect _x + rect _w - 1 , rect _y , 0 , rect _h , scale , translate _x , translate _y ) ; // right
draw _dashes ( ctx , rect _x , rect _y + rect _h - 1 , rect _w - 1 , 0 , scale , translate _x , translate _y ) ; // bottom
draw _dashes ( ctx , rect _x , rect _y + 1 , 0 , 1 , scale , translate _x , translate _y ) ; // top left dangling bit???
} ;
} ) ( ) ;
2019-10-29 18:46:29 +00:00
( ( ) => {
2018-06-16 21:49:00 +00:00
2019-10-29 20:29:38 +00:00
const tessy = ( function initTesselator ( ) {
2018-06-16 21:49:00 +00:00
// function called for each vertex of tesselator output
function vertexCallback ( data , polyVertArray ) {
2019-12-14 22:49:23 +00:00
// window.console && console.log(data[0], data[1]);
2018-06-16 21:49:00 +00:00
polyVertArray [ polyVertArray . length ] = data [ 0 ] ;
polyVertArray [ polyVertArray . length ] = data [ 1 ] ;
}
function begincallback ( type ) {
if ( type !== libtess . primitiveType . GL _TRIANGLES ) {
2019-12-14 22:49:23 +00:00
window . console && console . log ( ` Expected TRIANGLES but got type: ${ type } ` ) ;
2018-06-16 21:49:00 +00:00
}
}
function errorcallback ( errno ) {
2019-12-14 22:49:23 +00:00
window . console && console . log ( 'error callback' ) ;
window . console && console . log ( ` error number: ${ errno } ` ) ;
2018-06-16 21:49:00 +00:00
}
// callback for when segments intersect and must be split
function combinecallback ( coords , data , weight ) {
2019-12-14 22:49:23 +00:00
// window.console && console.log('combine callback');
2018-06-16 21:49:00 +00:00
return [ coords [ 0 ] , coords [ 1 ] , coords [ 2 ] ] ;
}
function edgeCallback ( flag ) {
// don't really care about the flag, but need no-strip/no-fan behavior
2019-12-14 22:49:23 +00:00
// window.console && console.log('edge flag: ' + flag);
2018-06-16 21:49:00 +00:00
}
2019-10-29 20:29:38 +00:00
const tessy = new libtess . GluTesselator ( ) ;
2018-06-16 21:49:00 +00:00
// tessy.gluTessProperty(libtess.gluEnum.GLU_TESS_WINDING_RULE, libtess.windingRule.GLU_TESS_WINDING_POSITIVE);
tessy . gluTessCallback ( libtess . gluEnum . GLU _TESS _VERTEX _DATA , vertexCallback ) ;
tessy . gluTessCallback ( libtess . gluEnum . GLU _TESS _BEGIN , begincallback ) ;
tessy . gluTessCallback ( libtess . gluEnum . GLU _TESS _ERROR , errorcallback ) ;
tessy . gluTessCallback ( libtess . gluEnum . GLU _TESS _COMBINE , combinecallback ) ;
tessy . gluTessCallback ( libtess . gluEnum . GLU _TESS _EDGE _FLAG , edgeCallback ) ;
return tessy ;
} ) ( ) ;
function triangulate ( contours ) {
// libtess will take 3d verts and flatten to a plane for tesselation
// since only doing 2d tesselation here, provide z=1 normal to skip
// iterating over verts only to get the same answer.
tessy . gluTessNormal ( 0 , 0 , 1 ) ;
2019-10-29 20:29:38 +00:00
const triangleVerts = [ ] ;
2018-06-16 21:49:00 +00:00
tessy . gluTessBeginPolygon ( triangleVerts ) ;
2019-10-29 20:29:38 +00:00
for ( let i = 0 ; i < contours . length ; i ++ ) {
2018-06-16 21:49:00 +00:00
tessy . gluTessBeginContour ( ) ;
2019-10-29 20:29:38 +00:00
const contour = contours [ i ] ;
for ( let j = 0 ; j < contour . length ; j += 2 ) {
const coords = [ contour [ j ] , contour [ j + 1 ] , 0 ] ;
2018-06-16 21:49:00 +00:00
tessy . gluTessVertex ( coords , coords ) ;
}
tessy . gluTessEndContour ( ) ;
}
tessy . gluTessEndPolygon ( ) ;
return triangleVerts ;
}
2019-10-29 20:29:38 +00:00
let gl ;
let positionLoc ;
2018-06-16 21:49:00 +00:00
function initWebGL ( canvas ) {
gl = canvas . getContext ( 'webgl' , { antialias : false } ) ;
2019-10-23 20:42:12 +00:00
window . WEBGL _lose _context = gl . getExtension ( "WEBGL_lose_context" ) ;
2019-10-29 20:29:38 +00:00
const program = createShaderProgram ( ) ;
2018-06-16 23:57:13 +00:00
positionLoc = gl . getAttribLocation ( program , 'position' ) ;
2018-06-16 21:49:00 +00:00
gl . enableVertexAttribArray ( positionLoc ) ;
}
2018-06-16 23:57:13 +00:00
function initArrayBuffer ( triangleVertexCoords ) {
2018-06-16 21:49:00 +00:00
// put triangle coordinates into a WebGL ArrayBuffer and bind to
// shader's 'position' attribute variable
2019-10-29 20:29:38 +00:00
const rawData = new Float32Array ( triangleVertexCoords ) ;
const polygonArrayBuffer = gl . createBuffer ( ) ;
2018-06-16 21:49:00 +00:00
gl . bindBuffer ( gl . ARRAY _BUFFER , polygonArrayBuffer ) ;
gl . bufferData ( gl . ARRAY _BUFFER , rawData , gl . STATIC _DRAW ) ;
gl . vertexAttribPointer ( positionLoc , 2 , gl . FLOAT , false , 0 , 0 ) ;
2018-06-16 23:57:13 +00:00
return triangleVertexCoords . length / 2 ;
}
2018-06-16 21:49:00 +00:00
function createShaderProgram ( ) {
// create vertex shader
2019-10-29 20:29:38 +00:00
const vertexSrc = [
2018-06-16 21:49:00 +00:00
'attribute vec4 position;' ,
'void main() {' ,
2019-10-29 21:00:31 +00:00
' /* already in normalized coordinates, so just pass through */' ,
' gl_Position = position;' ,
2018-06-16 21:49:00 +00:00
'}'
] . join ( '' ) ;
2019-10-29 20:29:38 +00:00
const vertexShader = gl . createShader ( gl . VERTEX _SHADER ) ;
2018-06-16 21:49:00 +00:00
gl . shaderSource ( vertexShader , vertexSrc ) ;
gl . compileShader ( vertexShader ) ;
if ( ! gl . getShaderParameter ( vertexShader , gl . COMPILE _STATUS ) ) {
2019-12-14 22:49:23 +00:00
window . console && console . log (
2019-10-29 21:16:33 +00:00
` Vertex shader failed to compile. Log: ${ gl . getShaderInfoLog ( vertexShader ) } `
2018-06-16 21:49:00 +00:00
) ;
}
// create fragment shader
2019-10-29 20:29:38 +00:00
const fragmentSrc = [
2018-06-16 21:49:00 +00:00
'precision mediump float;' ,
'void main() {' ,
2019-10-29 21:00:31 +00:00
' gl_FragColor = vec4(0, 0, 0, 1);' ,
2018-06-16 21:49:00 +00:00
'}'
] . join ( '' ) ;
2019-10-29 20:29:38 +00:00
const fragmentShader = gl . createShader ( gl . FRAGMENT _SHADER ) ;
2018-06-16 21:49:00 +00:00
gl . shaderSource ( fragmentShader , fragmentSrc ) ;
gl . compileShader ( fragmentShader ) ;
if ( ! gl . getShaderParameter ( fragmentShader , gl . COMPILE _STATUS ) ) {
2019-12-14 22:49:23 +00:00
window . console && console . log (
2019-10-29 21:16:33 +00:00
` Fragment shader failed to compile. Log: ${ gl . getShaderInfoLog ( fragmentShader ) } `
2018-06-16 21:49:00 +00:00
) ;
}
// link shaders to create our program
2019-10-29 20:29:38 +00:00
const program = gl . createProgram ( ) ;
2018-06-16 21:49:00 +00:00
gl . attachShader ( program , vertexShader ) ;
gl . attachShader ( program , fragmentShader ) ;
gl . linkProgram ( program ) ;
gl . useProgram ( program ) ;
return program ;
}
2019-10-29 20:29:38 +00:00
const op _canvas _webgl = document . createElement ( 'canvas' ) ;
const op _canvas _2d = document . createElement ( 'canvas' ) ;
const op _ctx _2d = op _canvas _2d . getContext ( "2d" ) ;
2018-06-16 21:49:00 +00:00
2018-06-27 21:11:36 +00:00
initWebGL ( op _canvas _webgl ) ;
2019-12-09 03:57:09 +00:00
let warning _tid ;
2019-10-22 03:43:16 +00:00
op _canvas _webgl . addEventListener ( "webglcontextlost" , ( e ) => {
e . preventDefault ( ) ;
2019-12-09 03:57:09 +00:00
window . console && console . warn ( "WebGL context lost" ) ;
2019-10-23 20:41:09 +00:00
clamp _brush _sizes ( ) ;
2019-12-09 03:57:09 +00:00
warning _tid = setTimeout ( ( ) => {
show _error _message ( "The WebGL context was lost. You may need to refresh the web page, or restart your computer." ) ;
} , 3000 ) ;
2019-10-22 03:43:16 +00:00
} , false ) ;
op _canvas _webgl . addEventListener ( "webglcontextrestored" , ( ) => {
initWebGL ( op _canvas _webgl ) ;
2019-10-23 20:42:12 +00:00
2019-12-09 03:57:09 +00:00
window . console && console . warn ( "WebGL context restored" ) ;
clearTimeout ( warning _tid ) ;
2019-10-23 20:41:09 +00:00
clamp _brush _sizes ( ) ;
2019-12-12 02:57:02 +00:00
// TODO: cachebust memoized brushes
2019-12-09 03:57:09 +00:00
// redraw tool options
$G . triggerHandler ( "option-changed" ) ;
2019-10-22 03:43:16 +00:00
} , false ) ;
2018-06-16 21:49:00 +00:00
2019-10-23 20:41:09 +00:00
function clamp _brush _sizes ( ) {
2019-10-29 20:29:38 +00:00
const max _size = 100 ;
2019-10-23 20:41:09 +00:00
if ( brush _size > max _size ) {
brush _size = max _size ;
show _error _message ( ` Brush size clamped to ${ max _size } ` ) ;
}
if ( pencil _size > max _size ) {
pencil _size = max _size ;
show _error _message ( ` Pencil size clamped to ${ max _size } ` ) ;
}
if ( stroke _size > max _size ) {
stroke _size = max _size ;
show _error _message ( ` Stroke size clamped to ${ max _size } ` ) ;
}
}
2019-10-29 18:46:29 +00:00
window . draw _line _strip = ( ctx , points ) => {
2018-06-17 16:47:20 +00:00
draw _polygon _or _line _strip ( ctx , points , true , false , false ) ;
2018-06-16 23:18:05 +00:00
} ;
2019-10-29 18:46:29 +00:00
window . draw _polygon = ( ctx , points , stroke , fill ) => {
2018-06-17 16:47:20 +00:00
draw _polygon _or _line _strip ( ctx , points , stroke , fill , true ) ;
2018-06-16 23:18:05 +00:00
} ;
2018-06-17 16:47:20 +00:00
function draw _polygon _or _line _strip ( ctx , points , stroke , fill , close _path ) {
2018-06-29 03:46:07 +00:00
// this must be before stuff is done with op_canvas
// otherwise update_brush_for_drawing_lines calls render_brush calls draw_ellipse calls draw_polygon calls draw_polygon_or_line_strip
// trying to use the same op_canvas
// (also, avoiding infinite recursion by checking for stroke; assuming brushes will never have outlines)
if ( stroke && stroke _size > 1 ) {
update _brush _for _drawing _lines ( stroke _size ) ;
}
2019-10-29 20:29:38 +00:00
const stroke _color = ctx . strokeStyle ;
const fill _color = ctx . fillStyle ;
2018-06-16 21:49:00 +00:00
2019-10-29 20:29:38 +00:00
const numPoints = points . length ;
const numCoords = numPoints * 2 ;
2018-06-16 21:49:00 +00:00
2018-06-18 19:53:59 +00:00
if ( numPoints === 0 ) {
return ;
}
2019-10-29 20:29:38 +00:00
let x _min = + Infinity ;
let x _max = - Infinity ;
let y _min = + Infinity ;
let y _max = - Infinity ;
2019-11-03 15:36:34 +00:00
for ( const { x , y } of points ) {
2018-06-16 21:49:00 +00:00
x _min = Math . min ( x , x _min ) ;
x _max = Math . max ( x , x _max ) ;
y _min = Math . min ( y , y _min ) ;
y _max = Math . max ( y , y _max ) ;
}
x _max += 1 ;
y _max += 1 ;
2018-06-28 23:14:32 +00:00
x _min -= 1 ;
y _min -= 1 ;
2018-06-16 21:49:00 +00:00
2018-06-27 21:11:36 +00:00
op _canvas _webgl . width = x _max - x _min ;
op _canvas _webgl . height = y _max - y _min ;
gl . viewport ( 0 , 0 , op _canvas _webgl . width , op _canvas _webgl . height ) ;
2018-06-16 21:49:00 +00:00
2019-10-29 20:29:38 +00:00
const coords = new Float32Array ( numCoords ) ;
2019-09-21 15:59:30 +00:00
for ( let i = 0 ; i < numPoints ; i ++ ) {
2018-06-27 21:11:36 +00:00
coords [ i * 2 + 0 ] = ( points [ i ] . x - x _min ) / op _canvas _webgl . width * 2 - 1 ;
coords [ i * 2 + 1 ] = 1 - ( points [ i ] . y - y _min ) / op _canvas _webgl . height * 2 ;
2018-06-16 21:49:00 +00:00
// TODO: investigate: does this cause resolution/information loss? can we change the coordinate system?
2018-06-17 00:05:49 +00:00
}
2018-06-16 23:18:05 +00:00
2018-06-16 21:49:00 +00:00
if ( fill ) {
2019-10-29 20:29:38 +00:00
const contours = [ coords ] ;
const polyTriangles = triangulate ( contours ) ;
2019-09-21 15:59:30 +00:00
let numVertices = initArrayBuffer ( polyTriangles ) ;
2018-06-17 16:36:16 +00:00
gl . clear ( gl . COLOR _BUFFER _BIT ) ;
2018-06-16 23:57:13 +00:00
gl . drawArrays ( gl . TRIANGLES , 0 , numVertices ) ;
2018-06-17 05:27:14 +00:00
2018-06-27 21:11:36 +00:00
op _canvas _2d . width = op _canvas _webgl . width ;
op _canvas _2d . height = op _canvas _webgl . height ;
2018-06-17 16:47:20 +00:00
2018-06-27 21:11:36 +00:00
op _ctx _2d . drawImage ( op _canvas _webgl , 0 , 0 ) ;
replace _colors _with _swatch ( op _ctx _2d , fill _color , x _min , y _min ) ;
ctx . drawImage ( op _canvas _2d , x _min , y _min ) ;
2018-06-16 21:49:00 +00:00
}
if ( stroke ) {
2018-06-17 05:27:14 +00:00
if ( stroke _size > 1 ) {
2019-10-29 20:29:38 +00:00
const stroke _margin = ~ ~ ( stroke _size * 1.1 ) ;
2018-06-17 16:47:20 +00:00
2019-10-29 20:29:38 +00:00
const op _canvas _x = x _min - stroke _margin ;
const op _canvas _y = y _min - stroke _margin ;
2018-06-29 03:23:57 +00:00
2018-06-27 21:11:36 +00:00
op _canvas _2d . width = x _max - x _min + stroke _margin * 2 ;
op _canvas _2d . height = y _max - y _min + stroke _margin * 2 ;
2019-09-21 15:59:30 +00:00
for ( let i = 0 ; i < numPoints - ( close _path ? 0 : 1 ) ; i ++ ) {
2019-10-29 20:29:38 +00:00
const point _a = points [ i ] ;
const point _b = points [ ( i + 1 ) % numPoints ] ;
2018-06-29 03:46:07 +00:00
// Note: update_brush_for_drawing_lines way above
2018-06-27 21:11:36 +00:00
draw _line _without _pattern _support (
op _ctx _2d ,
2019-09-21 15:59:30 +00:00
point _a . x - op _canvas _x ,
point _a . y - op _canvas _y ,
point _b . x - op _canvas _x ,
point _b . y - op _canvas _y ,
2018-06-17 05:27:14 +00:00
stroke _size
2018-06-27 21:11:36 +00:00
) ;
2018-06-17 05:27:14 +00:00
}
2018-06-17 15:41:52 +00:00
2019-10-11 00:24:19 +00:00
replace _colors _with _swatch ( op _ctx _2d , stroke _color , op _canvas _x , op _canvas _y ) ;
ctx . drawImage ( op _canvas _2d , op _canvas _x , op _canvas _y ) ;
2018-06-17 05:27:14 +00:00
} else {
2019-09-21 15:59:30 +00:00
let numVertices = initArrayBuffer ( coords ) ;
2018-06-17 16:36:16 +00:00
gl . clear ( gl . COLOR _BUFFER _BIT ) ;
2018-06-17 16:47:20 +00:00
gl . drawArrays ( close _path ? gl . LINE _LOOP : gl . LINE _STRIP , 0 , numVertices ) ;
2018-06-16 21:49:00 +00:00
2018-06-27 21:11:36 +00:00
op _canvas _2d . width = op _canvas _webgl . width ;
op _canvas _2d . height = op _canvas _webgl . height ;
2018-06-17 16:47:20 +00:00
2018-06-27 21:11:36 +00:00
op _ctx _2d . drawImage ( op _canvas _webgl , 0 , 0 ) ;
replace _colors _with _swatch ( op _ctx _2d , stroke _color , x _min , y _min ) ;
ctx . drawImage ( op _canvas _2d , x _min , y _min ) ;
2018-06-17 05:27:14 +00:00
}
}
2019-09-21 15:22:35 +00:00
}
2018-06-16 21:49:00 +00:00
2019-10-29 18:46:29 +00:00
window . copy _contents _within _polygon = ( canvas , points , x _min , y _min , x _max , y _max ) => {
2018-06-18 19:28:27 +00:00
// Copy the contents of the given canvas within the polygon given by points bounded by x/y_min/max
2018-06-18 19:53:59 +00:00
x _max = Math . max ( x _max , x _min + 1 ) ;
y _max = Math . max ( y _max , y _min + 1 ) ;
2019-10-29 20:29:38 +00:00
const width = x _max - x _min ;
const height = y _max - y _min ;
2018-06-18 19:28:27 +00:00
// TODO: maybe have the cutout only the width/height of the bounds
2019-11-03 15:36:34 +00:00
// const cutout = make_canvas(width, height);
2019-10-29 21:43:46 +00:00
const cutout = make _canvas ( canvas ) ;
2018-06-18 19:28:27 +00:00
cutout . ctx . save ( ) ;
cutout . ctx . globalCompositeOperation = "destination-in" ;
draw _polygon ( cutout . ctx , points , false , true ) ;
cutout . ctx . restore ( ) ;
2019-10-29 21:43:46 +00:00
const cutout _crop = make _canvas ( width , height ) ;
2018-06-18 19:28:27 +00:00
cutout _crop . ctx . drawImage ( cutout , x _min , y _min , width , height , 0 , 0 , width , height ) ;
return cutout _crop ;
}
2018-06-27 21:11:36 +00:00
// TODO: maybe shouldn't be external...
2019-10-29 18:46:29 +00:00
window . draw _with _swatch = ( ctx , x _min , y _min , x _max , y _max , swatch , callback ) => {
2019-10-29 20:29:38 +00:00
const stroke _margin = ~ ~ ( stroke _size * 1.1 ) ;
2018-06-27 21:11:36 +00:00
x _max = Math . max ( x _max , x _min + 1 ) ;
y _max = Math . max ( y _max , y _min + 1 ) ;
op _canvas _2d . width = x _max - x _min + stroke _margin * 2 ;
op _canvas _2d . height = y _max - y _min + stroke _margin * 2 ;
2019-10-29 20:29:38 +00:00
const x = x _min - stroke _margin ;
const y = y _min - stroke _margin ;
2018-06-27 21:11:36 +00:00
op _ctx _2d . save ( ) ;
op _ctx _2d . translate ( - x , - y ) ;
callback ( op _ctx _2d ) ;
op _ctx _2d . restore ( ) ; // for replace_colors_with_swatch!
replace _colors _with _swatch ( op _ctx _2d , swatch , x , y ) ;
ctx . drawImage ( op _canvas _2d , x , y ) ;
// for debug:
// ctx.fillStyle = "rgba(255, 0, 255, 0.1)";
// ctx.fillRect(x, y, op_canvas_2d.width, op_canvas_2d.height);
}
2018-06-16 21:49:00 +00:00
} ) ( ) ;