// based on Conal Elliott: ``Functional Images'', // in The Fun of Programming, pp. 131--150 (2003) import java.awt.image.BufferedImage import java.awt.Color import javax.imageio.ImageIO import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream import scala.math._; type Point = (Double,Double) type Image[a] = Point => a type Region = Image[Boolean] def vstrip: Region = p => { val (x,_) = p x.abs < 0.5 } def checker: Region = p => { val (x,y) = p (x.floor + y.floor) % 2 == 0 } def distO: Image[Double] = p => { val (x,y) = p sqrt(x*x + y*y) } def altRings: Region = p=> distO(p).floor % 2 == 0 def fromPolar (p: Point): Point = { val (rho,theta) = p (rho * cos(theta), rho * sin(theta)) } def toPolar (p: Point): Point = { val (x,y) = p (distO (x,y), atan2(y,x)) } def polarChecher (n: Int) : Region = { def sc (p:(Double,Double)) : (Double,Double) = { val (rho,theta) = p (rho,theta*n/Pi) } p => { checker(sc(toPolar(p))) } } def wavDist: Image[Double] = p => (1 + cos(Pi * distO(p))) / 2 type Frac = Double type Color = (Frac, Frac, Frac, Frac) val invisible: Color = (1,1,1,0) val white: Color = (1,1,1,1) val red: Color = (0,0,1,1) val green: Color = (0,1,0,1) val blue: Color = (1,0,0,1) val cyan: Color = (1,1,0,1) val magenta: Color = (1,0,1,1) val yellow: Color = (0,1,1,1) val black: Color = (0,0,0,1) def getRed(i: Color): Frac = i._3 def getGreen(i: Color): Frac = i._2 def getBlue(i: Color): Frac = i._1 def getAlpha(i: Color): Frac = i._4 def colorRGBA(r: Frac, g: Frac, b: Frac, a: Frac): Color = (b, g, r, a) def hsb(h: Double, s: Double, v: Double): Color = if (s == 0) (0, 0, 0, 1) else { val h360: Double = h * 360 val i0: Int = h360.toInt val f0: Double = h360 - i0 val hi: Int = if (f0>=0) (i0 % 360) else (i0-1) % 360 val hf: Double = if (f0>=0) f0 else f0+1 val hue: Double = hi + hf val saturation: Double = if (s>1) 1 else s val value: Double = if (v>1) 1 else v val h1: Int = hi / 60 val f: Double = hue / 60 - h1 val p: Double = value * (1-saturation) val q: Double = value * (1-f*saturation) val t: Double = value * (1-(1-f)*saturation) h1 match { case 0 => (p, t, v, 1) case 1 => (p, v, q, 1) case 2 => (t, v, p, 1) case 3 => (v, q, p, 1) case 4 => (v, p, t, 1) case 5 => (q, p, v, 1) } } def lerpC(w:Double, c1: Color, c2: Color) = { def h(x1: Frac, x2: Frac) = w*x1 + (1-w)*x2 colorRGBA(h(getRed(c1),getRed(c2)), h(getGreen(c1),getGreen(c2)), h(getBlue(c1),getBlue(c2)), h(getAlpha(c1),getAlpha(c2))) } def overC(c1: Color, c2: Color) = { val a = getAlpha(c1) def h(x1: Frac, x2: Frac) = a*x1 + (1-a)*x2 colorRGBA(h(getRed(c1),getRed(c2)), h(getGreen(c1),getGreen(c2)), h(getBlue(c1),getBlue(c2)), h(getAlpha(c1),getAlpha(c2))) } type ImageC = Image[Color] def over(top: ImageC, bot: ImageC): ImageC = p => { overC(top(p), bot(p)) } def lift1[A,B,C](h: B=>C) = (h1: A=>B) => (p: A) => h(h1(p)) def lift2[A,B1,B2,C](h: (B1,B2)=>C) = (h1:A=>B1,h2:A=>B2) => (p: A) => h(h1(p), h2(p)) def lift3[A,B1,B2,B3,C](h: (B1,B2,B3)=>C) = (h1: A=>B1, h2: A=>B2, h3: A=>B3) => (p: A) => h(h1(p), h2(p), h3(p)) def cond[T] (f1: Region, f2: Image[T], f3: Image[T]): Image[T] = p => { if (f1(p)) f2(p) else f3(p) } def lerpI(r: Image[Double],im1: ImageC, im2: ImageC): ImageC = lift3(lerpC)(r, im1, im2) def const[T](a: T) : Image[T] = p => a val empty = const(invisible) val whiteI = const(white) val blackI = const(black) val redI = const(red) val blueI = const(blue) val greenI = const(green) val cyanI = const(cyan) val magentaI = const(magenta) val yellowI = const(yellow) val ybRings = lerpI(wavDist, blueI, yellowI) def bwIm(reg: Region) = cond(reg, blackI, whiteI) type Transform = Point => Point type Vector = (Double, Double) def translateP(dp: Vector): Transform = p => { val (dx,dy) = dp val (x,y) = p (x+dx,y+dy) } def scaleP(s: Vector): Transform = p => { val (sx,sy) = s val (x,y) = p (sx*x,sy*y) } def uscaleP(s: Double): Transform = scaleP((s,s)) def rotateP (t: Double) : Transform = p => { val (x, y) = p val c = cos(t) val s = sin(t) (x*c-y*s, y*c+x*s) } type Filter[a] = Image[a] => Image[a] def translate[T](v: Vector): Filter[T] = im => { val (dx,dy) = v im compose translateP((-dx,-dy)) } def scale[T](v: Vector): Filter[T] = im => { val (sx,sy) = v im compose scaleP((1/sx,1/sy)) } def uscale[T](s: Double): Filter[T] = im => im compose uscaleP(1/s) def rotate[T](t: Double): Filter[T] = im => im compose rotateP (-t) def swirlP (r: Double) : Transform = p => rotateP (distO(p) * 2 * Pi / r) (p) def swirl[T](r: Double) : Filter[T] = im => im compose swirlP(-r) type FilterC = Filter[Color] type Time = Double type Anim[T] = Time => Image[T] def xPos(p: Point): Boolean = { val (x,y) = p x > 0 } def yPos(p: Point): Boolean = { val (x,y) = p y > 0 } val universeR: Region = const(true) val emptyR: Region = const(false) val compR: Region => Region = lift1((b: Boolean) => !b) val capR: (Region, Region) => Region = lift2((b1: Boolean, b2: Boolean) => b1 && b2) val cupR: (Region, Region) => Region = lift2((b1: Boolean, b2: Boolean) => b1 || b2) val xorR: (Region, Region) => Region = lift2((b1: Boolean, b2: Boolean) => b1 != b2) def setminusR(r1: Region, r2: Region): Region = capR(r1, compR(r2)) def udisk(p: Point): Boolean = distO(p) < 1 def annulus(inner: Frac): Region = setminusR(udisk, uscale(inner)(udisk)) def radReg(n: Double): Region = { def test(p: Point): Boolean = { val (_,t) = p (t * n / Pi).floor % 2 == 0 } test _ compose toPolar } def wedgeAnnulus(inner: Double, n: Double) = capR(annulus(inner), radReg(n)) def shiftXor(r: Double): Filter[Boolean] = reg => { def reg1(d: Double) = translate((d,0))(reg) xorR(reg1(r), reg1(-r)) } def xorRs(rs: List[Region]): Region = { rs.foldLeft(emptyR)(xorR) } def xorgon(n: Int, r: Double): Filter[Boolean] = (reg: Region) => { def rf(i: Int): Region = translate (fromPolar((r, i * 2 * Pi / n))) (reg) xorRs((0 to (n-1) toList).map(rf)) } def crop(reg: Region): FilterC = im => cond(reg, im, empty) def polarXf(xf: Transform): Transform = fromPolar _ compose xf compose toPolar _ val radInvertP: Transform = polarXf (p => { val (r, t) = p (1/r, t) }) def radInvert[T](im: Image[T]): Image[T] = im compose radInvertP def rippleRadP(n: Double, s: Double): Transform = polarXf (p => { val (r,t) = p (r * (1 + s * sin (n*t)), t) }) def rippleRad[T](n: Double, s: Double): Filter[T] = im => im compose rippleRadP(n, -s) def cropRad(r: Double): FilterC = crop (uscale(r)(udisk)) def circleLimit(radius: Double): FilterC = im => { def xf(p: Point): Point = { val (r,t) = p (radius*r/(radius-r),t) } cropRad(radius)(im compose polarXf(xf)) } def image2Arr2(xmin: Double, ymin: Double, xmax: Double, ymax: Double, width: Int, height: Int) : ImageC => BufferedImage = im => { val buf = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR) val off = buf.createGraphics val xstep = (xmax-xmin)/width val ystep = (ymax-ymin)/height for (j <- 0 until height) { val y = ymin + (j+0.5)*ystep for (i <- 0 until width) { val x = xmin + (i+0.5)*xstep val c = im((x,y)) off.setPaint(new java.awt.Color(getRed(c).toFloat, getGreen(c).toFloat, getBlue(c).toFloat, getAlpha(c).toFloat)) off.fillRect(i, j, 1, 1) } } buf } def image2Arr(xmax: Double, ymax: Double, width: Int, height: Int) : ImageC => BufferedImage = image2Arr2(-xmax, -ymax, xmax, ymax, width, height) def bufferedImage2png(buf: BufferedImage): Array[Byte] = { val out = new ByteArrayOutputStream // In some ocations, java.io.tmpdir is set to "C:/Windows/" ??? ImageIO.setUseCache(false) ImageIO.write(buf, "png", out) out.toByteArray } def bufferedImage2pngFile(buf: BufferedImage) { val f = new File("tmpS.png") val fo = new FileOutputStream(f) // In some ocations, java.io.tmpdir is set to "C:/Windows/" ??? ImageIO.setUseCache(false) ImageIO.write(buf, "png", fo) fo.close() } def myImage : ImageC = cond(swirl(1)(vstrip), redI, blueI) def test (im: ImageC) = { val bi = image2Arr(3.5, 3.5, 256, 256)(im) bufferedImage2pngFile(bi) } // bufferedImage2pngFile(image2Arr(2.5,2.5,256,256)(cond(swirl(1)(vstrip), redI, whiteI))) // bufferedImage2pngFile(image2Arr(11,11,256,256)(over(circleLimit(10)(cond(checker, blueI, whiteI)),whiteI))) // bufferedImage2pngFile(image2Arr(1.1,1.1,256,256)(cond(radInvert(checker),greenI,yellowI))) // bufferedImage2pngFile(image2Arr(5, 5, 256, 256)(rippleRad(8, 0.3)(lerpI(wavDist,cyanI,whiteI)))) // bufferedImage2pngFile(image2Arr(3.5, 3.5, 256, 256)(cond(xorgon(8, 7.0/4)(altRings), magentaI, whiteI)))