mirror of
https://github.com/direct-img/vibegif.direct-img.link.git
synced 2026-10-29 19:54:51 +00:00
Black ink, screentone and white only: reaction tropes (gaan shock, gloom corner, menacing aura, crow silence, petrify), comedy and action (harisen, iai draw, kendo, kyudo, miko), school life (locker letters, zero test, rooftop wind, shared umbrella), town and home (omikuji, shrine prayer, tanabata, floating lantern, alarm smash) and fantasy (yurei, oni, dragon, android, summoning circle, knight, pirate, detective, princess). gen files can now export rim=<px> for a white sticker border, so solid black ink still shows on the black page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
6.6 KiB
JavaScript
73 lines
6.6 KiB
JavaScript
// manga knight girl raising her sword to her face in salute with a glint, then sweeping it down; cape and helm plume blowing
|
|
import{createCanvas}from'@napi-rs/canvas'
|
|
const{sin,cos,PI,max,min}=Math,T=2*PI
|
|
export const frames=16,delay=80,rim=4
|
|
const TL=createCanvas(6,6),tx=TL.getContext('2d');tx.fillStyle='#111';for(const[a,b]of[[1.5,1.5],[4.5,4.5]])tx.beginPath(),tx.arc(a,b,1.05,0,T),tx.fill()
|
|
const ease=x=>x<0?0:x>1?1:x*x*(3-2*x)
|
|
const eye=(c,x,y,d,tn)=>{
|
|
c.fillStyle='#fff';c.beginPath();c.ellipse(x,y,14,16,0,0,T);c.fill()
|
|
c.save();c.beginPath();c.ellipse(x,y+2,11,14,0,0,T);c.clip();c.fillStyle=tn;c.fillRect(x-12,y-12,24,30);c.fillStyle='#111';c.fillRect(x-12,y-12,24,10);c.restore()
|
|
c.lineWidth=2.5;c.beginPath();c.ellipse(x,y+2,11,14,0,0,T);c.stroke()
|
|
c.fillStyle='#111';c.beginPath();c.ellipse(x,y+3,4.5,6.5,0,0,T);c.fill()
|
|
c.fillStyle='#fff';c.beginPath();c.ellipse(x-4,y-3,3.5,4.5,-.4,0,T);c.fill();c.beginPath();c.arc(x+4,y+8,2,0,T);c.fill()
|
|
c.lineWidth=7;c.beginPath();c.moveTo(x-17,y-4);c.quadraticCurveTo(x,y-15,x+17,y-4);c.moveTo(x+d*16,y-5);c.lineTo(x+d*22,y-9);c.stroke()
|
|
// determined brows
|
|
c.lineWidth=5;c.beginPath();c.moveTo(x-d*14,y-20);c.lineTo(x+d*12,y-25);c.stroke()
|
|
}
|
|
const star=(c,x,y,s)=>{c.beginPath();for(let k=0;k<8;k++){const r=k%2?s*.22:s,a=k*PI/4;c.lineTo(x+r*cos(a),y+r*sin(a))}c.closePath();c.fill();c.stroke()}
|
|
export default(c,t)=>{
|
|
const tn=c.createPattern(TL,'repeat'),w=sin(t*T*2),s=ease((t-.05)/.15)-ease((t-.55)/.15)
|
|
const hx=150-26*s,hy=206-48*s,a=-.75-(PI/2-.75)*s
|
|
// cape behind, ink black, blowing out to the left
|
|
const cp=()=>{c.beginPath();c.moveTo(90,118);c.quadraticCurveTo(60,170,34+6*w,236);c.quadraticCurveTo(50,230,62+4*w,246);c.quadraticCurveTo(76,236,92,250);c.lineTo(168,250);c.quadraticCurveTo(170,170,150,118);c.closePath()}
|
|
c.lineWidth=5;c.fillStyle='#111';cp();c.fill();c.stroke()
|
|
c.strokeStyle='#fff';c.lineWidth=2.5;c.beginPath();c.moveTo(80,150);c.quadraticCurveTo(62,196,48+5*w,232);c.stroke();c.strokeStyle='#111'
|
|
// screentone ponytail streaming left
|
|
const pt=()=>{c.beginPath();c.moveTo(92,52);c.bezierCurveTo(60,48,30,70,16+4*w,96+6*w);c.quadraticCurveTo(40,88,46+3*w,104);c.quadraticCurveTo(58,84,88,80);c.closePath()}
|
|
c.lineWidth=5;c.fillStyle='#fff';pt();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(0,40,100,80);c.restore();pt();c.stroke()
|
|
c.lineWidth=2.5;c.beginPath();c.moveTo(84,64);c.quadraticCurveTo(56,64,30+4*w,90+5*w);c.moveTo(84,74);c.quadraticCurveTo(62,78,46+3*w,96);c.stroke()
|
|
// breastplate and pauldrons
|
|
const bp=()=>{c.beginPath();c.moveTo(96,124);c.quadraticCurveTo(84,170,92,232);c.lineTo(150,232);c.quadraticCurveTo(158,170,144,124);c.closePath()}
|
|
c.lineWidth=5;c.fillStyle='#fff';bp();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(132,120,30,120);c.restore();bp();c.stroke()
|
|
c.lineWidth=3;c.beginPath();c.moveTo(120,150);c.lineTo(120,190);c.moveTo(106,166);c.lineTo(134,166);c.stroke()
|
|
c.beginPath();c.moveTo(92,206);c.quadraticCurveTo(120,214,150,206);c.stroke()
|
|
c.fillStyle='#fff';c.lineWidth=4;for(const[x,d]of[[94,-1],[146,1]]){c.beginPath();c.ellipse(x,130,20,14,d*.3,PI,0);c.lineTo(x+d*16,142);c.quadraticCurveTo(x,148,x-d*16,140);c.closePath();c.fill();c.stroke();c.beginPath();c.ellipse(x,134,13,7,d*.3,PI,0);c.stroke()}
|
|
const arms=()=>{
|
|
// arms out to the gauntlets on the hilt
|
|
for(const sx of[96,146])for(const[l,st]of[[18,'#111'],[10,'#fff']])c.lineWidth=l,c.strokeStyle=st,c.beginPath(),c.moveTo(sx,140),c.quadraticCurveTo((sx+hx)/2+(sx<120?-10:14),(140+hy)/2+16,hx,hy),c.stroke()
|
|
c.strokeStyle='#111'
|
|
// sword
|
|
c.save();c.translate(hx,hy);c.rotate(a)
|
|
c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.moveTo(16,-6);c.lineTo(98,-5);c.lineTo(110,0);c.lineTo(98,5);c.lineTo(16,6);c.closePath();c.fill();c.stroke()
|
|
c.lineWidth=2;c.beginPath();c.moveTo(20,0);c.lineTo(94,0);c.stroke()
|
|
c.lineWidth=4;c.beginPath();c.rect(10,-18,7,36);c.fill();c.stroke()
|
|
c.fillStyle='#111';c.beginPath();c.rect(-16,-4,26,8);c.fill();c.stroke();c.fillStyle='#fff';c.beginPath();c.arc(-20,0,6,0,T);c.fill();c.stroke()
|
|
c.restore()
|
|
for(const o of[-6,6]){c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.ellipse(hx+o*cos(a),hy+o*sin(a),9,9,0,0,T);c.fill();c.stroke()}
|
|
if(s>.95){c.fillStyle='#fff';c.lineWidth=2.5;const g=1-Math.abs((t-.38)/.12);if(g>0)star(c,hx+112*cos(a),hy+112*sin(a)+2,14*g)}
|
|
}
|
|
if(s<.5)arms()
|
|
// head
|
|
c.save();c.translate(118,84);c.scale(.6,.6);c.translate(-128,-120)
|
|
c.lineWidth=7;c.fillStyle='#fff';c.beginPath();c.moveTo(74,96);c.bezierCurveTo(70,152,100,184,128,186);c.bezierCurveTo(156,184,186,152,182,96);c.bezierCurveTo(182,44,74,44,74,96);c.fill();c.stroke()
|
|
eye(c,103,142,-1,tn);eye(c,153,142,1,tn)
|
|
c.lineWidth=3;for(const d of[-1,1])for(const k of[-5,1])c.beginPath(),c.moveTo(128+d*44+k-3,168),c.lineTo(128+d*44+k+3,160),c.stroke()
|
|
c.lineWidth=4;c.beginPath();c.moveTo(120,172);c.lineTo(136,172);c.stroke()
|
|
// screentone side locks framing the face
|
|
for(const d of[-1,1]){const sl=()=>{c.beginPath();c.moveTo(128+d*50,96);c.quadraticCurveTo(128+d*64,150,128+d*58,196);c.quadraticCurveTo(128+d*50,184,128+d*44,190);c.quadraticCurveTo(128+d*48,140,128+d*40,104);c.closePath()}
|
|
c.lineWidth=6;c.fillStyle='#fff';sl();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(0,0,256,256);c.restore();sl();c.stroke()}
|
|
// screentone bangs
|
|
const bg=()=>{c.beginPath();c.moveTo(72,150);c.bezierCurveTo(56,80,90,48,128,48);c.bezierCurveTo(166,48,200,80,184,150);c.quadraticCurveTo(180,118,172,108);c.quadraticCurveTo(162,114,156,120);c.quadraticCurveTo(150,104,138,98)
|
|
c.quadraticCurveTo(132,112,122,116);c.quadraticCurveTo(116,102,104,98);c.quadraticCurveTo(98,112,90,118);c.quadraticCurveTo(84,110,80,104);c.quadraticCurveTo(72,124,72,150);c.closePath()}
|
|
c.lineWidth=7;c.fillStyle='#fff';bg();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(0,0,256,256);c.restore();bg();c.stroke()
|
|
// half helm with a cheek guard line and a blowing plume
|
|
c.fillStyle='#fff';c.lineWidth=7;c.beginPath();c.moveTo(72,92);c.bezierCurveTo(70,36,186,36,184,92);c.quadraticCurveTo(128,72,72,92);c.fill();c.stroke()
|
|
c.lineWidth=4;c.beginPath();c.moveTo(80,80);c.quadraticCurveTo(128,62,176,80);c.stroke()
|
|
c.beginPath();c.moveTo(128,40);c.lineTo(128,72);c.stroke()
|
|
const pl=()=>{c.beginPath();c.moveTo(124,42);c.bezierCurveTo(110,4,72,-4,36+8*w,8+6*w);c.quadraticCurveTo(66,14,74,22);c.quadraticCurveTo(46,26,30+6*w,40+6*w);c.quadraticCurveTo(80,40,118,50);c.closePath()}
|
|
c.lineWidth=6;c.fillStyle='#fff';pl();c.fill();c.stroke()
|
|
c.lineWidth=3;c.beginPath();c.moveTo(118,40);c.quadraticCurveTo(84,14,50+6*w,12+4*w);c.moveTo(112,46);c.quadraticCurveTo(80,32,46+5*w,36+5*w);c.stroke()
|
|
c.restore()
|
|
if(s>=.5)arms()
|
|
}
|