LoginSignup
3
1

More than 3 years have passed since last update.

ハートをエロくする

Last updated at Posted at 2020-05-14



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

ハートを描いてみる

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

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

image.png

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

image.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 はパラメータが面倒なので、三次ベジエで近似します。

//  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 )
}

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

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()

ハートをエロくする

本題です。

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

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

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

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

See the Pen yLYqvdP by Satachito (@satachito) on CodePen.

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

image.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つと違うのは、y0にして、先っちょの部分も接線接続しているところです。

<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

最後に

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

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1