/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx.p5 import cc.drx._ import processing.core._ import processing.core.PConstants._ import processing.core.PApplet._ import P5._ object Draw{ import Color._ /**this can be used to reset the draw states to some known state*/ def defaults(g:PGraphics):PGraphics = { //g.background(White) g.rectMode(CORNERS) //draw rect from NW to SE g.ellipseMode(CENTER) //specify an ellipse by x, y radius ?? g.noStroke g.noFill g } private def rawImageOf(img:Img):PImage = img match { case ImgFile(file, _) => imgCache(file) case _ => ??? //TODO Img wrapped Pimage } //--utility syntax on processing methods implicit class RichPSurface(val surface:PSurface) extends AnyVal{ def setIcon(img:Img) = surface.setIcon(rawImageOf(img)) } implicit class RichPImage(val img:PImage) extends AnyVal{ def scale(scale:Double):PImage = { img.resize((img.width*scale).round.toInt, (img.height*scale).round.toInt) img } } implicit class RichPGraphics(val g:PGraphics) extends AnyVal{ def !(property:Style.Property):Unit = this ! Style(Set(property)) def !(shape:Shape):Unit = shape draw g def !(shapes:Traversable[Shape]):Unit = shapes foreach {_ draw g} def draw(drawFunc: => Unit):PGraphics = { g.beginDraw g.pushMatrix g.pushStyle drawFunc g.popStyle g.popMatrix g.endDraw g } } /**style here has grown to be a compound of graphics drawing state*/ implicit class RichStyle(val style:Style) extends AnyVal with Shape{ import Style._ def draw(implicit g:PGraphics):Unit = { //accumulator variables to collect settings var vertAlign:Option[Int] = None //processing int var horzAlign:Option[Int] = None var strokeWasSet = false var fillWasSet = false style foreach { case Default => defaults(g) case Background(c) => g.background(c) case Fill(c) => g.fill(c); fillWasSet = true case FillNone => g.noFill; fillWasSet = false case Stroke(c) => g.stroke(c); strokeWasSet = true case StrokeNone => g.noStroke; strokeWasSet = false case Weight(w) => g.strokeWeight(w) case f:Font => f.draw(g) //g.textFont(f.pFont, f.size) case Align(horz,vert) => ??? //FIXME need to add the features here case x:AlignHorizontal => horzAlign = Some(x match { case Left => LEFT case Right => RIGHT case Center => CENTER }) case x:AlignVertical => vertAlign = Some(x match{ case Top => TOP case Midline => CENTER case Bottom => BOTTOM }) case ScaleProperty(t:Vec) => g.scale(t.x,t.y) case Translate(t:Vec) => g.translate(t.x,t.y) case Rotate(r:Angle) => g.rotate(r.rad) case Transform(t,r,s) => g.translate(t.x, t.y) g.rotate(r.rad) g.scale(s.x,s.y) } //if neither stroke or fill is set then let the fill or stroke pass through if(strokeWasSet && !fillWasSet) g.noFill if( fillWasSet && !strokeWasSet) g.noStroke if(vertAlign.isDefined || horzAlign.isDefined) g.textAlign(horzAlign getOrElse CENTER, vertAlign getOrElse CENTER) } def draw(drawFunc: => Unit)(implicit g:PGraphics):Unit = { g.pushMatrix g.pushStyle draw //draw the style drawFunc g.popStyle g.popMatrix } } /**Generic shape class*/ //TODO can this be brought up to drx instead of drx.p5?? trait Shape extends Any{ //---required methods @deprecated("use (g ! shape ~ style) or (shape ~ style draw g) instead","v0.1.18") def draw(style:Style)(implicit g:PGraphics):Unit = style.draw{draw} def draw(implicit g:PGraphics):Unit //---derived methods //**Attach a style to a shape*/ def ~(style:Style):StyledShape = StyledShape(this,style) //**initialize shape with a style */ def ~(property:Style.Property):StyledShape = StyledShape(this,Style()~property) /** provide a quick automatic way to set a fill or stroke * all shapes have a stroke even if they can be filled (but the ClosedShape overrides this to provide a fill color) */ def ~(color:Color):StyledShape = this ~ Style.Stroke(color) } /**these are shapes that have an area and default to coloring by area rather than stroke*/ trait ClosedShape extends Any with Shape { override def ~(color:Color):StyledShape = this ~ Style.Fill(color) } /*turn a generic shape into a styled *///TODO can this be brought up to drx so StypedShapes are first class and part of containers for other draw contexts case class StyledShape(shape:Shape,style:Style) extends Shape{ def draw(implicit g:PGraphics):Unit = style.draw{shape.draw(g)}(g) override def draw(overrideStyle:Style)(implicit g:PGraphics):Unit = overrideStyle.draw{shape.draw(g)}(g) override def ~(mergeStyle:Style):StyledShape = StyledShape(shape, style ~ mergeStyle) override def ~(addProperty:Style.Property):StyledShape = StyledShape(shape,style~addProperty) } case class GroupedShape(shapes:Traversable[Shape]) extends Shape{ def draw(implicit g:PGraphics):Unit = shapes.foreach{_.draw(g)} } //FIXME TODO this is a temporary class to support the new generic core Shape.Styled class instead of the implicit p5.Draw.StyledShape implicit class DrxShapeStyled(val styledShape:cc.drx.Shape.Styled) extends AnyVal with Shape{ def draw(implicit g:PGraphics):Unit = new DrawContextP5(g) ! styledShape } @deprecated("This is techincally not a Shape","v0.1.18") implicit class RichVec(val vec:Vec) extends AnyVal with Shape{ @deprecated("use (g ! vec ~ 1) instead","v0.1.18") def draw(implicit g:PGraphics):Unit = g.ellipse(vec.x,vec.y,1,1) @deprecated("use (g ! vec ~ d) instead","v0.1.18") def draw(d:Double=1.0)(implicit g:PGraphics):Unit = g.ellipse(vec.x,vec.y,d,d) @deprecated("use (g ! vec ~ string) instead","v0.1.18") def draw(text:String)(implicit g:PGraphics):Unit = g.text(text,vec.x,vec.y) } implicit class RichBezier(val z:Bezier) extends AnyVal with Shape{ def draw(implicit g:PGraphics):Unit = g.bezier(z.a.x, z.a.y, z.ca.x, z.ca.y, z.cb.x, z.cb.y, z.b.x,z.b.y) //general bezier } implicit class RichStar(val star:Star) extends AnyVal with ClosedShape{ def draw(implicit g:PGraphics):Unit = { g.beginShape for(v <- star.vertices) g.vertex(v.x, v.y) g.endShape(CLOSE) } } implicit class RichArrow(val arrow:Arrow) extends AnyVal with Shape{ //not a closed shaped but the head gets the fill color by the stroke def draw(implicit g:PGraphics):Unit = { val color:Color = Color(g.strokeColor) val lineWeight:Double=g.strokeWeight //grab the currently set strokeWeight val headSize:Double=lineWeight*8 val a = arrow.line.a val b = arrow.line.b val vel = b - a if(vel.range != 0){ //g.strokeWeight(lineWeight) g.stroke(color) //--triangle vec boid val head = b - vel.mag(headSize) g.line(a.x,a.y, head.x,head.y) val ortho = (vel x Vec.z) mag (headSize/2) val right = head + ortho val left = head - ortho g.noStroke g.fill(color) g.beginShape g.vertex(b.x, b.y) g.vertex(right.x, right.y) g.vertex(left.x, left.y) g.endShape } } } implicit class RichLine(val line:Line) extends AnyVal with Shape{ def a = line.a def b = line.b def draw(implicit g:PGraphics):Unit = g.line(a.x,a.y, b.x,b.y) @deprecated("use (g ! line ~ Style.Weight(weight)) instead","v0.1.18") def draw(weight:Double)(implicit g:PGraphics):Unit = { g.pushStyle g.strokeWeight(weight) g.line(a.x,a.y, b.x,b.y) g.popStyle } @deprecated("use g ! Arrow(line) instead","0.2.0-angle") def drawArrow(color:Color,lineWeight:Double=1, headSize:Double=8)(implicit g:PGraphics):Unit = { val vel = b - a if(vel.range != 0) {g.pushStyle g.strokeWeight(lineWeight) g.stroke(color) //--triangle vec boid val head = b - vel.mag(headSize) g.line(a.x,a.y, head.x,head.y) val ortho = (vel x Vec.z) mag (headSize/2) val right = head + ortho val left = head - ortho g.noStroke g.fill(color) g.beginShape g.vertex(b.x, b.y) g.vertex(right.x, right.y) g.vertex(left.x, left.y) g.endShape g.popStyle } } } //TODO use a open and closed path couter-part Path and PathClosed ShapeClosed implicit class RichPath(val path:Path) extends AnyVal with Shape{ def draw(implicit g:PGraphics):Unit = { //TODO we shouldn't require a push and pop style inside this shape g.pushStyle g.noFill g.beginShape path.vertices foreach { case v:Vec => g.vertex(v.x, v.y) case v:BezierVertex => g.bezierVertex(v.ca.x,v.ca.y, v.cb.x,v.cb.y, v.b.x, v.b.y) } g.endShape g.popStyle } } //TODO deprecate the overly specific shape a Boid like shape implicit class RichState(val state:State) extends AnyVal with Shape{ def pos = state.pos def vel = state.vel //def acc = state.acc def draw(implicit g:PGraphics):Unit = draw(1) def draw(size:Double=1)(implicit g:PGraphics):Unit = if(vel.range != 0){ //--triangle vec boid val head = pos + (vel mag (size*4)) val ortho = vel x Vec.z mag size val right = pos + ortho val left = pos - ortho g.beginShape g.vertex(head.x, head.y) g.vertex(right.x, right.y) g.vertex(left.x, left.y) g.endShape } } implicit class RichText(val text:Text) extends AnyVal with ClosedShape{ def draw(implicit g:PGraphics):Unit = g.text(text.value, text.pos.x, text.pos.y) } //--cache to auto load and keep resources, this removes the initialization requirement and implementation details to the surface drawn context private val fontCache:Cache[Style.Font,PFont] = Cache{font:Style.Font => import java.awt.{Font => JFont} def mkFont(in: => Input) = Try{JFont.createFont(JFont.TRUETYPE_FONT, in.is).deriveFont(JFont.PLAIN,font.size)} val jFont = mkFont(File(font.name).in) orElse mkFont(Input.resource(File(font.name)).get) getOrElse //the .get is wrapped in the mkFont Try to catch errors new JFont(font.name, JFont.PLAIN, font.size.round.toInt) new PFont(jFont, true) //use smoothing by default } private val svgCache = Cache{xml:String => new PShapeSVG(processing.data.XML.parse(xml)) } private val imgCache = Cache{f:File => val bytes:Array[Byte] = if(f.isFile) f.in.toByteArray else Input.resource(f).map{_.toByteArray}.getOrElse(Array.empty[Byte]) val pImg = new PImage(new javax.swing.ImageIcon(bytes).getImage) pImg.format = ARGB pImg } def emptyCache() = { //or use the word clear fontCache.empty svgCache.empty imgCache.empty } implicit class RichSvg(val svg:Svg) extends AnyVal with Shape{ // @deprecated("use svg.draw(g) or g ! svg instead","0.2.13") def pShape = svgCache(svg.xml) def draw(implicit g:PGraphics):Unit = { val p:PShape = svgCache(svg.xml) // p.disableStyle // val box = Rect(p.width,p.height) fitIn svg.box //fit the picture size including aspect ratio into the drawing box //TODO check if rotations can also work here... //g.shape(p, svg.box.a.x, svg.box.a.y, svg.box.width, svg.box.height) // g.fill(Red) g.shape(p) } } implicit class RichImg(val img:Img) extends AnyVal with Shape{ @deprecated("use img.draw(g) or g ! img instead","0.2.13") def pImg:PImage = rawImageOf(img) def draw(implicit g:PGraphics):Unit = { val p:PImage = rawImageOf(img) val box = Rect(p.width,p.height) fitIn img.box //fit the picture size including aspect ratio into the drawing box //TODO check if rotations can also work here... g.image(p, box.a.x, box.a.y, box.width, box.height) } } implicit class RichFont(val font:Style.Font) extends AnyVal{ // @deprecated("use font.draw(g) or g ! font instead","0.2.13") def pFont:PFont = fontCache(font) /**compute the width of represented character*/ @deprecated("use font.width(String) instead","0.2.13") def width(c:Char):Double = pFont width c /**compute the width of represented string*/ def width(str:String):Double = { val p = pFont //store to prevent re-calling the def (0d /: str){case (a,c) => a + p.width(c)}*font.size } def draw(g:PGraphics):Unit = g.textFont(pFont, font.size) } implicit class RichCirc(val c:Circ) extends AnyVal with ClosedShape{ def draw(implicit g:PGraphics):Unit = g.ellipse(c.c.x,c.c.y, c.r*2,c.r*2) } implicit class RichEllipse(val e:Ellipse) extends AnyVal with ClosedShape{ def draw(implicit g:PGraphics):Unit = { if(e.rotation == Angle(0)) g.ellipse(e.c.x,e.c.y, e.r.x*2,e.r.y*2) else{ g.pushMatrix g.translate(e.c.x, e.c.y) g.rotate(e.rotation.rad) g.ellipse(0,0, e.r.x*2,e.r.y*2) g.popMatrix } } } implicit class RichArc(val a:Arc) extends AnyVal with Shape{ def draw(implicit g:PGraphics):Unit = { g.pushStyle g.noFill g.strokeWeight(a.w) g.strokeCap(SQUARE) val w = a.r*2 //g.ellipseMode(is set to CENTER as default which means RADIUS ???) g.arc(a.c.x,a.c.y, w,w, a.angle.min.rad, a.angle.max.rad, OPEN) g.popStyle } } implicit class RichRect(val r:Rect) extends AnyVal with ClosedShape{ def draw(implicit g:PGraphics):Unit = g.rect(r.a.x,r.a.y, r.b.x,r.b.y) //assuming rectMode(CORNERS) @deprecated("dont include styles in the drawing context","v0.2.15") def draw(cornerRadius:Double)(implicit g:PGraphics):Unit = g.rect(r.a.x,r.a.y, r.b.x,r.b.y, cornerRadius) } implicit class RichAxes[A,B](val a:Axes[A,B]) extends AnyVal with ClosedShape{ def draw(implicit g:PGraphics):Unit = { //TODO add draw ticks and detect font size and spacings (and get tick stroke color from the set fill color a.xTicks foreach {t => g ! (t + 3.x) ~ Style.Top} //auto center a.yTicks foreach {t => g ! (t - 3.x) ~ Style.Right} //auto midline } } implicit class RichTri(val tri:Tri) extends AnyVal with ClosedShape{ def draw(implicit g:PGraphics):Unit = { g.beginShape() g.vertex(tri.a.x, tri.a.y) g.vertex(tri.b.x, tri.b.y) g.vertex(tri.c.x, tri.c.y) g.endShape(CLOSE) } } implicit class RichPoly(val poly:Poly) extends AnyVal with ClosedShape{ def draw(implicit g:PGraphics):Unit = { g.beginShape() poly.vertices.foreach{v => g.vertex(v.x, v.y)} g.endShape(CLOSE) } } }