HTML5 Canvas vs. Android Canvas

# はじめに
Wear OSウォッチフェイスのプロトタイプをHTML5 Canvas + JavaScriptで組んでいたのですが、これをAndroid Canvas + Kotlinに移植しようとしたら微妙につまずいたので調査してメモ。
~~そもそもこの開発フローが間違っている気がしなくもない。~~

# 対比表
| HTML5 Canvas<br>(JavaScript) | Android Canvas<br>(Kotlin) |
| --- | --- |
| ctx.clearRect(x,y,width,height); | cnv.clipOutRect(x,y,x+width,y+height) | paint.setStyle(Paint.Style.FILL)<br>paint.setColor(Color.argb(a,r,g,b))<br>cnv.drawRect(x,y,x+width,y+height,paint) |
| ctx.lineWidth=width;<br>ctx.strokeStyle="rgba(r,g,b,a)";<br>ctx.strokeRect(x,y,width,height); | paint.setStrokeWidth(width)<br>paint.setStyle(Paint.Style.STROKE)<br>paint.setColor(Color.argb(a,r,g,b))<br>cnv.drawRect(x,y,x+width,y+height,paint) |
| ctx.fillStyle="rgba(r,g,b,a)";<br>ctx.fillText(text,x,y); | paint.setStrokeWidth(0.0f)<br>paint.setColor(Color.argb(a,r,g,b))<br>cnv.drawText(text,x,y,paint) |
| ctx.lineWidth=width;<br>ctx.strokeStyle="rgba(r,g,b,a)";<br>ctx.strokeText(text,x,y); | paint.setStrokeWidth(width)<br>paint.setStyle(Paint.Style.STROKE)<br>paint.setColor(Color.argb(a,r,g,b))<br>cnv.drawText(text,x,y,paint) |
| text_metrics=ctx.measureText(text); | rect=Rect()<br>paint.getTextBounds(text,0,text.length(),rect) |
| ctx.lineWidth=width; | paint.setStrokeWidth(0.0f) |
| ctx.lineCap=style_str; | paint.setStrokeCap(Paint.Cap) |
| ctx.lineJoin=style_str; | paint.setStrokeJoin(Paint.Join) |
| ctx.miterLimit=limit_num; | paint.setStrokeMiter(limit_num) |
| ctx.getLineDash(); | paint.getPathEffect() |
| ctx.setLineDash(array);<br>ctx.lineDashOffset=*offset*; | effect=DashPathEffect(array,offset)<br>paint.setPathEffect(effect) |
| ctx.font=font_str; | paint.typeface=typeface |
| ctx.textAlign=style_str; | paint.setTextAlign(Paint.Align) |
| ctx.textBaseline=style_str; | **直接的にはない**<br>※計算して配置をずらす必要がある |
| ctx.direction=style_str;<br>ctx.fillText(text,x,y); | cnv.drawTextRun (text,s,e,cStart,cEnd,x,y,isRtl,paint) |
| ctx.fillStyle=style_str; | paint.setStyle(Paint.Style.FILL)<br>paint.setColor(Color.argb(a,r,g,b)) |
| ctx.strokeStyle=style_str; | paint.setStyle(Paint.Style.STROKE)<br>paint.setColor(Color.argb(a,r,g,b)) |
| grad=ctx.createConicGradient(start_angle,x,y);<br>grad.addColorStop(step,color);<br>grad.addColorStop(step,color);<br>... | SweepGradient (x,y,colors,positions) |
| ctx.createLinearGradient(x0,y0,x1,y1)<br>grad.addColorStop(step,color1)<br>grad.addColorStop(step,color2)<br> | LinearGradient (x0,y0,x1,y1,color0,color1,Shader.TileMode)  |
| ctx.createRadialGradient(x0,y0,r0,x1,y1,r1);<br>grad.addColorStop(step,color);<br>grad.addColorStop(step,color);<br>... | RadialGradient (cX,cY,radius,colors,stops,Shader.TileMode) |
| ctx.createPattern(image,repetition); | BitmapShader (bitmap,Shader.TileMode,Shader.TileMode) |
| ctx.shadowBlur=rad;<br>ctx.shadowOffsetX=x;<br>ctx.shadowOffsetY=y;<br>ctx.shadowColor="rgba(r,g,b,a)"; | paint.setShadowLayer (rad,x,y,argb(a,r,g,b)) |
| ctx.beginPath();<br>ctx.closePath(); | path=Path()<br>path.rewind()<br>path.close() |
| ctx.moveTo(x,y); | path.moveTo(x,y) |
| ctx.lineTo(x,y); | path.lineTo(x,y); |
| ctx.bezierCurveTo(x1,y1,x2,y2,x3,y3); | path.cubicTo(x1,y1,x2,y2,x3,y3) |
| ctx.quadraticCurveTo(x1,y1,x2,y2); | quadTo(x1,y1,x2,y2) |
| ctx.arcTo(x1,y1,x2,y2,rad); | addArc(x1,y1,x2,y2,0,rad) |
| ctx.rect(x,y,width,height); | addRect (x,y,x+width,y+height,Path.Direction) |
| ctx.fill(); | paint.setStyle(Paint.Style.FILL)<br>cnv.drawPath(path,paint) |
| ctx.stroke(); | paint.setStyle(Paint.Style.STROKE)<br>cnv.drawPath(path,paint) |
| ctx.drawFocusIfNeeded(element); | **直接的にはなさそう？** |
| ctx.scrollPathIntoView(); | **直接的にはなさそう？** |
| ctx.clip(); | cnv.clipOutPath(path) |
| ctx.isPointInPath(x,y); | **直接的にはなさそう？** |
| ctx.isPointInStroke(x,y); | **直接的にはなさそう？** |
| ctx.getTransform(); | cnv.getMatrix() |
| ctx.rotate(rad); | cnv.rotate(deg) **※角度単位違い** |
| ctx.scale(x,y);| cnv.scale(x,y) |
| ctx.translate(x,y); | cnv.translate(x,y) |
| ctx.transform(a,b,c,d,e,f); | matrix=Matrix()<br>matrix.mapVectors([a,b,c,d,e,f]) |
| ctx.setTransform(a,b,c,d,e,f); | matrix=Matrix()<br>matrix.mapVectors([a,b,c,d,e,f])<br>cnv.setMatrix(matrix) |
| ctx.resetTransform(); | cnv.restore()<br>**※Transform以外も戻る** |
| ctx.globalAlpha=alpha; | canvas.saveLayerAlpha (0,0,cnv.getWidth(),cnv.getHeight(),alpha)<br>**※設定はグローバル単位ではない** |
| ctx.globalCompositeOperation=type_str; | paint.setBlendMode(BlendMode)<br>**※設定はPaintオブジェクト単位** |
| ctx.drawImage(image,x,y); | cnv.drawBitmap(bitmap,x,y,null) |
| ctx.createImageData(width,height); | createBitmap (width,height, Bitmap.Config,true,ColorSpace) |
| ctx.getImageData(sx,sy,sw,sh); | **直接的にはない**<br>※CanvasからBitmapを生成する手法がある<br>　→ [画面に配置せずにViewからBitmapを生成する](https://qiita.com/kobakei/items/229d0ef78148dce9d84b) |
| ctx.putImageData(imageData,dx,dy); | cnv.drawBitmap(bitmap,x,y,null) |
| ctx.imageSmoothingEnabled=bool; | paint.setAntiAlias(bool) |
| ctx.imageSmoothingQuality=quality_str; | **なさそう？** |
| ctx.save(); | cnv.save() |
| ctx.restore(); | cnv.restore() |
| ctx.getContextAttributes(); | **一括では取れない？** |
| ctx.filter=filter_func; | cnv.setDrawFilter(DrawFilter) |

※ `ctx` は `CanvasRenderingContext2D` のオブジェクトです。
※ `cnv` は `android.graphics.Canvas` のオブジェクトです。
※ `paint` は `android.graphics.Paint` のオブジェクトです。

間違い、またはもっといい方法があればぜひコメント欄にてお知らせください。

# 参考資料
[CanvasRenderingContext2D](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter)
[android.graphics.Canvas](https://developer.android.com/reference/android/graphics/Canvas)
[android.graphics.Paint](https://developer.android.com/reference/android/graphics/Paint)

# さいごに
楽をしようとしたら、余計に手間がかかりました。(などと)