ハートをエロくする


<hr>
<b>プログラム人生の中で一度はハートを描いてくれって言われたことありませんか？</b>
<hr>

# ハートを描いてみる

というわけで早速ハートを描いてみます。

オーソドックスなハートってこんな形な気がします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73493/348b83a7-1115-d51b-7d5e-c8a68c60acf8.png)

これ、こんな風に点をとって描いてます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73493/177721eb-96b9-8a01-e071-d3d5e22a77e6.png)

原点をハートの先っちょに持ってってやって、ハートの幅を w、高さを h として図をみると、右半分のパスは次の3つで書けそうです。左半分はそれの鏡像でいいでしょう。

三次ベジエ：`(0,0)-(x,y)-(w/2,y1)-(w/2,y2)`
　　　円弧：`(w/2,y2)-(w/2,h)-(x1,h)`
　　　円弧：`(x1,h)-(x2,h)-(0,dy)`

canvas.getContext( '2d' )で取れる`CanvasRenderingContext2D`にハートの形のパスを構成する`heartPath`を仕込んじゃいましょう。
円弧を描く CanvasRenderingContext2D.arc はパラメータが面倒なので、三次ベジエで近似します。

```javascript
//	Curve factor	: 0.552285
const CF = ( -24 + Math.sqrt( 24 * 24 + 64 * 9 ) ) / 18

CanvasRenderingContext2D.prototype.heartPath = function( w, h, x, y, y1, y2, x1, x2, dy ) {
	x *= w
	y *= h
	y1 *= h
	y2 *= h
	x1 *= w
	x2 *= w
	dy *= h
	let cx = 0
	let cy = 0
	const
	MoveTo = ( x, y ) => {
		this.moveTo( x, y )
		cx = x
		cy = y
	}
	const
	CurveTo = ( x1, y1, x2, y2, x, y ) => {
		this.bezierCurveTo( x1, y1, x2, y2, x, y )
		cx = x
		cy = y
	}
	const
	ArcByQ = ( x1, y1, x, y ) => {
		CurveTo(
			x1 * CF + cx * ( 1 - CF )
		,	y1 * CF + cy * ( 1 - CF )
		,	x1 * CF + x * ( 1 - CF )
		,	y1 * CF + y * ( 1 - CF )
		,	x
		,	y
		)
	}

	const rH = w / 2
	MoveTo( 0, 0 )
	CurveTo( -x, y, -rH, y1, -rH, y2 )
	ArcByQ( -rH, h, -x1, h )
	ArcByQ( -x2, h, 0, dy )
	ArcByQ( +x2, h, +x1, h )
	ArcByQ( rH, h, rH, y2 )
	CurveTo( rH, y1, x, y, 0, 0 )
}
```

このオーソドックスなハートは以下のパラメータで描いています。

```javascript
const canvas = document.createElement( 'canvas' )
const w = 512	
canvas.width = w * 1.2
const h = 512
canvas.height = h * 1.2
const x = 0.05
const y = 0.10
const y1 = 0.40
const y2 = 0.72
const x1 = 0.25
const x2 = 0.05
const dy = 0.84
c2d.translate( w / 2, h )
c2d.scale( 1, -1 )
c2d.translate( canvas.width / 2, h )
c2d.heartPath( w, h, x, y, y1, y2, x1, x2, dy )
c2d.fillStyle = 'red'
c2d.fill()
```

# ハートをエロくする

本題です。

<b>プログラム人生の中で、一度はハートをエロくしてくれって言われたことありませんか？</b>

というわけで、早速ハートをエロくしてみます。

ハートマークの下部をしぼって、上部が円形になるようにすると、エロくなるみたいです。
要するに女体っぽくすればいいようです。

WebComponent 化して、CodePen でみてみます。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="satachito" data-slug-hash="yLYqvdP" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="yLYqvdP">
  <span>See the Pen <a href="https://codepen.io/satachito/pen/yLYqvdP">
  yLYqvdP</a> by Satachito (<a href="https://codepen.io/satachito">@satachito</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

CodePenが失敗したときの保険のためにイメージを貼っておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73493/315e597e-f423-6d9a-d969-3ad99623c083.png)

下のようなパラメータにしてみました。エロいかどうかは主観に基づいているのでほんとにエロく感じていただけているか不安なのですがいかがでしょうか？

```
<heart-element w=200 h=200 x=0.25 y=0.50 y1=0.50 y2=0.75 x1=0.25 x2=0.05 dy=0.80></heart-element>
```


# バレンタインのチョコの箱

ちょっと季節はずれな話題ですが、バレンタインの時にみるハート型のチョコの箱の形、チョコを入れるという役割とのトレードオフなのはわかりますが、なんか違和感ありますよね。

というわけで、バレンタインのチョコの箱を描いてみます。こんなパラメータでどうでしょう。
上2つと違うのは、`y`を`0`にして、先っちょの部分も接線接続しているところです。

```
<heart-element w=240 h=200 x=0.10 y=0 y1=0.30 y2=0.70 x1=0.20 x2=0.10 dy=0.93></heart-element>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73493/195c9336-8eb4-e3c8-aef8-155efee2fa3d.png)

# 最後に

エロいハートの作り方を知りたくて「エロいハート」でググったら仕事にならなくなりました。同様の悩みを抱えている方のために最後にこの記事で使った画像を全部表示するスタンドアローンで動く（ファイルをブラウザに食わせて動く）html ファイルを載せておきます。

```html
<script type=module>

	//	Curve factor	: 0.552285
	const CF = ( -24 + Math.sqrt( 24 * 24 + 64 * 9 ) ) / 18

	CanvasRenderingContext2D.prototype.heartPath = function( w, h, x, y, y1, y2, x1, x2, dy ) {
		x *= w
		y *= h
		y1 *= h
		y2 *= h
		x1 *= w
		x2 *= w
		dy *= h
		let	cx = 0
		let cy = 0
		const
		MoveTo = ( x, y ) => {
			this.moveTo( x, y )
			cx = x
			cy = y
		}
		const
		CurveTo = ( x1, y1, x2, y2, x, y ) => {
			this.bezierCurveTo( x1, y1, x2, y2, x, y )
			cx = x
			cy = y
		}
		const
		ArcByQ = ( x1, y1, x, y ) => {
			CurveTo(
				x1 * CF + cx * ( 1 - CF )
			,	y1 * CF + cy * ( 1 - CF )
			,	x1 * CF + x * ( 1 - CF )
			,	y1 * CF + y * ( 1 - CF )
			,	x
			,	y
			)
		}

		const rH = w / 2
		MoveTo( 0, 0 )
		CurveTo( -x, y, -rH, y1, -rH, y2 )
		ArcByQ( -rH, h, -x1, h )
		ArcByQ( -x2, h, 0, dy )
		ArcByQ( +x2, h, +x1, h )
		ArcByQ( rH, h, rH, y2 )
		CurveTo( rH, y1, x, y, 0, 0 )
	}

	class
	HeartElement extends HTMLElement {
		constructor() {
			super()
			const canvas = document.createElement( 'canvas' )
			const w = this.getAttribute( 'w' )
			const h = this.getAttribute( 'h' )
			canvas.setAttribute( 'width', w )
			canvas.setAttribute( 'height', h )
			this.appendChild( canvas )
			const c2d = canvas.getContext( '2d' )
			c2d.translate( w / 2, h )
			c2d.scale( 1, -1 )

			c2d.heartPath(
				w
			,	h
			,	Number( this.getAttribute( 'x' ) )
			,	Number( this.getAttribute( 'y' ) )
			,	Number( this.getAttribute( 'y1' ) )
			,	Number( this.getAttribute( 'y2' ) )
			,	Number( this.getAttribute( 'x1' ) )
			,	Number( this.getAttribute( 'x2' ) )
			,	Number( this.getAttribute( 'dy' ) )
			)

			c2d.fillStyle = 'red'
			c2d.fill()
		}
	}
	customElements.define( 'heart-element', HeartElement )

	class
	HeartExplanation extends HTMLElement {
		constructor() {
			super()
			const canvas = document.createElement( 'canvas' )
			this.appendChild( canvas )
			const c2d = canvas.getContext( '2d' )
			const w = 512	
			canvas.width = w * 1.2
			const h = 512
			canvas.height = h * 1.2
			const x = 0.05
			const y = 0.10
			const y1 = 0.40
			const y2 = 0.72
			const x1 = 0.25
			const x2 = 0.05
			const dy = 0.84
			c2d.translate( canvas.width / 2, h * 1.1 )
			c2d.font = '24px serif'
			c2d.fillText( '原点(0,0)', 0, 24 )
			c2d.fillText( 'x, y', 4 + x * w, -y * h )
			c2d.fillText( 'y1', 4 + w / 2, -y1 * h )
			c2d.fillText( 'y2', 4 + w / 2, -y2 * h )
			c2d.fillText( '(w/2,h)', -36 + w / 2, -h - 4 )
			c2d.fillText( 'x1', x1 * w, -h - 4 )
			c2d.fillText( 'x2', x2 * w, -h - 4 )
			c2d.fillText( 'dy', 0, -dy * h + 24 )
			c2d.scale( 1, -1 )

			c2d.heartPath( w, h, x, y, y1, y2, x1, x2, dy )
			c2d.stroke()

			c2d.beginPath()
			c2d.moveTo( 0, 0 )
			c2d.lineTo( x * w, y * h )
			c2d.moveTo( w / 2, y1 * h )
			c2d.lineTo( w / 2, h )
			c2d.lineTo( x2 * w, h )
			c2d.lineTo( 0, dy * h )
			c2d.strokeStyle = 'red'
			c2d.stroke()

			const
			FillCircle = ( x, y ) => {
				c2d.beginPath()
				c2d.ellipse( x, y, 4, 4, 0, 0, 2 * Math.PI )
				c2d.fill()
			}
			c2d.fillStyle = 'blue'
			FillCircle( x * w, y * h )
			FillCircle( w / 2, y1 * h )
			FillCircle( w / 2, y2 * h )
			FillCircle( w / 2, h )
			FillCircle( x1 * w, h )
			FillCircle( x2 * w, h )
			FillCircle( 0, dy * h )
		 }
	}
	customElements.define( 'heart-explanation', HeartExplanation )

</script>

<heart-explanation></heart-explanation>

<div>オーソドックス</div>
<heart-element w=200 h=200 x=0.05 y=0.10 y1=0.40 y2=0.72 x1=0.25 x2=0.05 dy=0.84></heart-element>
<br>
<br>

<div>ちょっとエロい？</div>
<heart-element w=200 h=200 x=0.25 y=0.50 y1=0.50 y2=0.75 x1=0.25 x2=0.05 dy=0.80></heart-element>
<br>
<br>

<div>バレンタインのチョコの箱</div>
<heart-element w=240 h=200 x=0.10 y=0 y1=0.30 y2=0.70 x1=0.20 x2=0.10 dy=0.93></heart-element>
<br>
<br>

<style>
* {
;	margin		: 0
;	padding		: 0
}
</style>

```