diff --git a/cmd/testimage.go b/cmd/testimage.go index cbd2747..847b0f2 100644 --- a/cmd/testimage.go +++ b/cmd/testimage.go @@ -17,19 +17,19 @@ func saveToPngFile(filePath string, m image.Image) { f, err := os.Open(filePath, os.O_CREAT|os.O_WRONLY, 0600) if err != nil { log.Println(err) - os.Exit(1) + return } defer f.Close() b := bufio.NewWriter(f) err = png.Encode(b, m) if err != nil { log.Println(err) - os.Exit(1) + return } err = b.Flush() if err != nil { log.Println(err) - os.Exit(1) + return } fmt.Printf("Wrote %s OK.\n", filePath) } @@ -52,29 +52,20 @@ func loadFromPngFile(filePath string) image.Image { } -func testBubble(gc draw2d.GraphicContext) { - gc.BeginPath() - gc.MoveTo(75, 25) - gc.QuadCurveTo(25, 25, 25, 62.5) - gc.QuadCurveTo(25, 100, 50, 100) - gc.QuadCurveTo(50, 120, 30, 125) - gc.QuadCurveTo(60, 120, 65, 100) - gc.QuadCurveTo(125, 100, 125, 62.5) - gc.QuadCurveTo(125, 25, 75, 25) - gc.Stroke() -} - func main() { - - source := loadFromPngFile("../resource/image/Varna_Railway_Station_HDR.png") + source := loadFromPngFile("../resource/image/TestAndroid.png") i := image.NewRGBA(1024, 768) gc := draw2d.NewImageGraphicContext(i) - gc.Scale(2, 0.5) - //gc.Translate(75, 25) +// gc.Scale(2, 0.5) + gc.Translate(float64(source.Bounds().Dx()/2), float64(source.Bounds().Dy()/2)) gc.Rotate(30 * math.Pi / 180) + gc.Translate(float64(-source.Bounds().Dx()/2), float64(-source.Bounds().Dy()/2)) + gc.Translate(75, 25) lastTime := time.Nanoseconds() gc.DrawImage(source) dt := time.Nanoseconds() - lastTime fmt.Printf("Draw image: %f ms\n", float64(dt)*1e-6) saveToPngFile("../resource/result/TestDrawImage.png", i) } + + diff --git a/draw2d/draw2d.go b/draw2d/draw2d.go index a73bf4f..0ac2393 100644 --- a/draw2d/draw2d.go +++ b/draw2d/draw2d.go @@ -200,7 +200,7 @@ func (gc *ImageGraphicContext) Restore() { } func (gc *ImageGraphicContext) DrawImage(img image.Image) { - DrawImage(img, gc.img, gc.current.tr, draw.Over, linearFilter) + DrawImage(img, gc.img, gc.current.tr, draw.Over, BilinearFilter) } func (gc *ImageGraphicContext) BeginPath() { diff --git a/draw2d/math.go b/draw2d/math.go index 703d9ed..9323818 100644 --- a/draw2d/math.go +++ b/draw2d/math.go @@ -21,3 +21,31 @@ func squareDistance(x1, y1, x2, y2 float64) float64 { dy := y2 - y1 return dx*dx + dy*dy } + +func min(x, y float64) float64 { + if x < y { + return x + } + return y +} + +func max(x, y float64) float64 { + if x > y { + return x + } + return y +} + +func minMax(x, y float64) (min, max float64) { + if x > y { + return y, x + } + return x, y +} + +func minUint32(a, b uint32) uint32 { + if a < b { + return a + } + return b +} diff --git a/draw2d/paint.go b/draw2d/paint.go index 8a156a5..be3bbd1 100644 --- a/draw2d/paint.go +++ b/draw2d/paint.go @@ -19,13 +19,6 @@ type NRGBAPainter struct { cr, cg, cb, ca uint32 } -func min(a, b uint32) uint32 { - if a < b { - return a - } - return b -} - // Paint satisfies the Painter interface by painting ss onto an image.RGBA. func (r *NRGBAPainter) Paint(ss []raster.Span, done bool) { b := r.Image.Bounds() @@ -57,9 +50,9 @@ func (r *NRGBAPainter) Paint(ss []raster.Span, done bool) { a := M - (r.ca*ma)/M da = (da*a + r.ca*ma) / M if da != 0 { - dr = min(M, (dr*a+r.cr*ma)/da) - dg = min(M, (dg*a+r.cg*ma)/da) - db = min(M, (db*a+r.cb*ma)/da) + dr = minUint32(M, (dr*a+r.cr*ma)/da) + dg = minUint32(M, (dg*a+r.cg*ma)/da) + db = minUint32(M, (db*a+r.cb*ma)/da) } else { dr, dg, db = 0, 0, 0 } @@ -71,9 +64,9 @@ func (r *NRGBAPainter) Paint(ss []raster.Span, done bool) { a := M - ma da = (da*a + r.ca*ma) / M if da != 0 { - dr = min(M, (dr*a+r.cr*ma)/da) - dg = min(M, (dg*a+r.cg*ma)/da) - db = min(M, (db*a+r.cb*ma)/da) + dr = minUint32(M, (dr*a+r.cr*ma)/da) + dg = minUint32(M, (dg*a+r.cg*ma)/da) + db = minUint32(M, (db*a+r.cb*ma)/da) } else { dr, dg, db = 0, 0, 0 } diff --git a/draw2d/rgba_interpolation.go b/draw2d/rgba_interpolation.go index 81683f1..7c260b1 100644 --- a/draw2d/rgba_interpolation.go +++ b/draw2d/rgba_interpolation.go @@ -5,12 +5,13 @@ import ( "image" "math" ) + type ImageFilter int const ( - linearFilter ImageFilter = iota - bilinearFilter - bicubicFilter + LinearFilter ImageFilter = iota + BilinearFilter + BicubicFilter ) //see http://pippin.gimp.org/image_processing/chap_resampling.html @@ -59,8 +60,8 @@ func getColorCubicRow(img image.Image, x, y, offset float64) image.Color { r1, g1, b1, a1 := c1.RGBA() r2, g2, b2, a2 := c2.RGBA() r3, g3, b3, a3 := c3.RGBA() - r, g, b, a := cubic(offset,float64(r0),float64(r1),float64(r2),float64(r3)), cubic(offset,float64(g0),float64(g1),float64(g2),float64(g3)), cubic(offset,float64(b0),float64(b1),float64(b2),float64(b3)), cubic(offset,float64(a0),float64(a1),float64(a2),float64(a3)) - return image.RGBAColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)} + r, g, b, a := cubic(offset, float64(r0), float64(r1), float64(r2), float64(r3)), cubic(offset, float64(g0), float64(g1), float64(g2), float64(g3)), cubic(offset, float64(b0), float64(b1), float64(b2), float64(b3)), cubic(offset, float64(a0), float64(a1), float64(a2), float64(a3)) + return image.RGBAColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)} } func getColorBicubic(img image.Image, x, y float64) image.Color { @@ -77,15 +78,15 @@ func getColorBicubic(img image.Image, x, y float64) image.Color { r1, g1, b1, a1 := c1.RGBA() r2, g2, b2, a2 := c2.RGBA() r3, g3, b3, a3 := c3.RGBA() - r, g, b, a := cubic(dy,float64(r0),float64(r1),float64(r2),float64(r3)), cubic(dy,float64(g0),float64(g1),float64(g2),float64(g3)), cubic(dy,float64(b0),float64(b1),float64(b2),float64(b3)), cubic(dy,float64(a0),float64(a1),float64(a2),float64(a3)) + r, g, b, a := cubic(dy, float64(r0), float64(r1), float64(r2), float64(r3)), cubic(dy, float64(g0), float64(g1), float64(g2), float64(g3)), cubic(dy, float64(b0), float64(b1), float64(b2), float64(b3)), cubic(dy, float64(a0), float64(a1), float64(a2), float64(a3)) return image.RGBAColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)} } -func cubic(offset,v0,v1,v2,v3 float64) uint32{ - // offset is the offset of the sampled value between v1 and v2 - return uint32((((( -7 * v0 + 21 * v1 - 21 * v2 + 7 * v3 ) * offset + - ( 15 * v0 - 36 * v1 + 27 * v2 - 6 * v3 ) ) * offset + - ( -9 * v0 + 9 * v2 ) ) * offset + (v0 + 16 * v1 + v2) ) / 18.0); +func cubic(offset, v0, v1, v2, v3 float64) uint32 { + // offset is the offset of the sampled value between v1 and v2 + return uint32(((((-7*v0+21*v1-21*v2+7*v3)*offset+ + (15*v0-36*v1+27*v2-6*v3))*offset+ + (-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0) } func compose(c1, c2 image.Color) image.Color { @@ -99,20 +100,42 @@ func compose(c1, c2 image.Color) image.Color { return image.RGBAColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)} } + func DrawImage(src image.Image, dest draw.Image, tr MatrixTransform, op draw.Op, filter ImageFilter) { - //width := float64(src.Bounds().Dx()) - //height := float64(src.Bounds().Dy()) - //tr.InverseTransform(&width, &height) - // TODO: find a x0, y0, x1, y1 that fits into dest 0, width, 0, height is too large - width := float64(dest.Bounds().Dx()) - height:= float64(dest.Bounds().Dy()) + b := src.Bounds() + x0, y0, x1, y1 := float64(b.Min.X), float64(b.Min.Y), float64(b.Max.X), float64(b.Max.Y) + tr.TransformRectangle(&x0, &y0, &x1, &y1) var x, y, u, v float64 - for x = 0; x < width; x++ { - for y = 0; y < height; y++ { + for x = x0; x < x1; x++ { + for y = y0; y < y1; y++ { u = x v = y tr.InverseTransform(&u, &v) - dest.Set(int(x), int(y), compose(dest.At(int(x), int(y)), getColorLinear(src, u, v))) + c1 := dest.At(int(x), int(y)) + var c2 image.Color + switch filter { + case LinearFilter: + c2 = getColorLinear(src, u, v) + case BilinearFilter: + c2 = getColorBilinear(src, u, v) + case BicubicFilter: + c2 = getColorBicubic(src, u, v) + } + var cr image.Color + switch op { + case draw.Over: + r1, g1, b1, a1 := c1.RGBA() + r2, g2, b2, a2 := c2.RGBA() + ia := M - a2 + r := ((r1 * ia) / M) + r2 + g := ((g1 * ia) / M) + g2 + b := ((b1 * ia) / M) + b2 + a := ((a1 * ia) / M) + a2 + cr = image.RGBAColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)} + default: + cr = c2 + } + dest.Set(int(x), int(y), cr) } } } diff --git a/draw2d/transform.go b/draw2d/transform.go index ff22ca2..a794483 100644 --- a/draw2d/transform.go +++ b/draw2d/transform.go @@ -26,6 +26,23 @@ func (tr MatrixTransform) Transform(points ...*float64) { } } +func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2*float64) { + x1 := *x2 + y1 := *y0 + x3 := *x0 + y3 := *y2 + tr.Transform(x0, y0, &x1, &y1, x2, y2, &x3, &y3) + *x0, x1 = minMax(*x0, x1) + *x2, x3 = minMax(*x2, x3) + *y0, y1 = minMax(*y0, y1) + *y2, y3 = minMax(*y2, y3) + + *x0 = min(*x0, *x2) + *y0 = min(*y0, *y2) + *x2 = max(x1, x3) + *y2 = max(y1, y3) +} + func (tr MatrixTransform) TransformRasterPoint(points ...*raster.Point) { for _, point := range points { x := float64(point.X) / 256 diff --git a/resource/result/TestDrawImage.png b/resource/result/TestDrawImage.png index 62fb065..ba1c4fb 100644 Binary files a/resource/result/TestDrawImage.png and b/resource/result/TestDrawImage.png differ