From b0b792a0e8a39f4ce822a8d4141c294ab700f419 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Tue, 31 May 2011 07:51:53 +0200 Subject: [PATCH] use fix point --- draw2d/curve/_testmain.go | 6 +- draw2d/raster/Makefile | 1 + draw2d/raster/coverage_table.go | 23 ++- draw2d/raster/fillerAA.go | 76 +++++--- draw2d/raster/fillerV1/fillerAA.go | 302 +++++++++++++++++++++++++++++ draw2d/raster/fixed_point.go | 17 ++ 6 files changed, 389 insertions(+), 36 deletions(-) create mode 100644 draw2d/raster/fillerV1/fillerAA.go create mode 100644 draw2d/raster/fixed_point.go diff --git a/draw2d/curve/_testmain.go b/draw2d/curve/_testmain.go index b411f51..eff0174 100644 --- a/draw2d/curve/_testmain.go +++ b/draw2d/curve/_testmain.go @@ -2,7 +2,7 @@ package main import "draw2d.googlecode.com/hg/draw2d/curve" import "testing" -import __os__ "os" +import __os__ "os" import __regexp__ "regexp" var tests = []testing.InternalTest{ @@ -13,8 +13,8 @@ var tests = []testing.InternalTest{ {"curve.TestCubicCurveParabolic", curve.TestCubicCurveParabolic}, {"curve.TestQuadCurve", curve.TestQuadCurve}, } -var benchmarks = []testing.InternalBenchmark{ // - {"curve.BenchmarkCubicCurveRec", curve.BenchmarkCubicCurveRec}, + +var benchmarks = []testing.InternalBenchmark{ {"curve.BenchmarkCubicCurveRec", curve.BenchmarkCubicCurveRec}, {"curve.BenchmarkCubicCurve", curve.BenchmarkCubicCurve}, {"curve.BenchmarkCubicCurveAdaptiveRec", curve.BenchmarkCubicCurveAdaptiveRec}, {"curve.BenchmarkCubicCurveAdaptive", curve.BenchmarkCubicCurveAdaptive}, diff --git a/draw2d/raster/Makefile b/draw2d/raster/Makefile index c907c3f..f5c93b2 100644 --- a/draw2d/raster/Makefile +++ b/draw2d/raster/Makefile @@ -6,6 +6,7 @@ GOFILES=\ polygon.go\ coverage_table.go\ fillerAA.go\ + fixed_point.go include $(GOROOT)/src/Make.pkg diff --git a/draw2d/raster/coverage_table.go b/draw2d/raster/coverage_table.go index df33d72..a9c0e3a 100644 --- a/draw2d/raster/coverage_table.go +++ b/draw2d/raster/coverage_table.go @@ -3,10 +3,25 @@ package raster var SUBPIXEL_OFFSETS_SAMPLE_8 = [8]float64{ - 5 / 8, 0 / 8, - 3 / 8, 6 / 8, - 1 / 8, 4 / 8, - 7 / 8, 2 / 8, + 5 / 8, + 0 / 8, + 3 / 8, + 6 / 8, + 1 / 8, + 4 / 8, + 7 / 8, + 2 / 8, +} + +var SUBPIXEL_OFFSETS_SAMPLE_8_FIXED = [8]Fix{ + Fix(SUBPIXEL_OFFSETS_SAMPLE_8[0]*FIXED_FLOAT_COEF), + Fix(SUBPIXEL_OFFSETS_SAMPLE_8[1]*FIXED_FLOAT_COEF), + Fix(SUBPIXEL_OFFSETS_SAMPLE_8[2]*FIXED_FLOAT_COEF), + Fix(SUBPIXEL_OFFSETS_SAMPLE_8[3]*FIXED_FLOAT_COEF), + Fix(SUBPIXEL_OFFSETS_SAMPLE_8[4]*FIXED_FLOAT_COEF), + Fix(SUBPIXEL_OFFSETS_SAMPLE_8[5]*FIXED_FLOAT_COEF), + Fix(SUBPIXEL_OFFSETS_SAMPLE_8[6]*FIXED_FLOAT_COEF), + Fix(SUBPIXEL_OFFSETS_SAMPLE_8[7]*FIXED_FLOAT_COEF), } var SUBPIXEL_OFFSETS_SAMPLE_16 = [16]float64{ diff --git a/draw2d/raster/fillerAA.go b/draw2d/raster/fillerAA.go index 6533f3f..5bc4055 100644 --- a/draw2d/raster/fillerAA.go +++ b/draw2d/raster/fillerAA.go @@ -8,13 +8,13 @@ import ( ) const ( - SUBPIXEL_SHIFT = 4 + SUBPIXEL_SHIFT = 3 SUBPIXEL_COUNT = 1 << SUBPIXEL_SHIFT ) -var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_16 +var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_8_FIXED -type SUBPIXEL_DATA uint16 +type SUBPIXEL_DATA uint8 type NON_ZERO_MASK_DATA_UNIT uint8 type Rasterizer8BitsSample struct { @@ -119,17 +119,52 @@ func (r *Rasterizer8BitsSample) RenderEvenOdd(img *image.RGBA, color *image.RGBA //! Adds an edge to be used with even-odd fill. func (r *Rasterizer8BitsSample) addEvenOddEdge(edge *PolygonEdge) { - x := edge.X - slope := edge.Slope - var ySub, mask SUBPIXEL_DATA + x := Fix(edge.X * FIXED_FLOAT_COEF) + slope := Fix(edge.Slope * FIXED_FLOAT_COEF) + slopeFix := Fix(0) + if (edge.LastLine - edge.FirstLine >= SLOPE_FIX_STEP) { + slopeFix = Fix(edge.Slope * SLOPE_FIX_STEP * FIXED_FLOAT_COEF) - (slope << SLOPE_FIX_SHIFT); + } + + var mask SUBPIXEL_DATA + var ySub uint32 var xp, yLine int for y := edge.FirstLine; y <= edge.LastLine; y++ { - ySub = SUBPIXEL_DATA(y & (SUBPIXEL_COUNT - 1)) - xp = (int)(x + SUBPIXEL_OFFSETS[ySub]) + ySub = uint32(y & (SUBPIXEL_COUNT - 1)) + xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT) mask = SUBPIXEL_DATA(1 << ySub) yLine = y >> SUBPIXEL_SHIFT r.MaskBuffer[yLine*r.BufferWidth+xp] ^= mask x += slope + if (y & SLOPE_FIX_MASK) == 0 { + x += slopeFix; + } + } +} + +//! Adds an edge to be used with non-zero winding fill. +func (r *Rasterizer8BitsSample) addNonZeroEdge(edge *PolygonEdge) { + x := Fix(edge.X * FIXED_FLOAT_COEF) + slope := Fix(edge.Slope * FIXED_FLOAT_COEF) + slopeFix := Fix(0) + if (edge.LastLine - edge.FirstLine >= SLOPE_FIX_STEP) { + slopeFix = Fix(edge.Slope * SLOPE_FIX_STEP * FIXED_FLOAT_COEF) - (slope << SLOPE_FIX_SHIFT); + } + var mask SUBPIXEL_DATA + var ySub uint32 + var xp, yLine int + winding := NON_ZERO_MASK_DATA_UNIT(edge.Winding) + for y := edge.FirstLine; y <= edge.LastLine; y++ { + ySub = uint32(y & (SUBPIXEL_COUNT - 1)) + xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT) + mask = SUBPIXEL_DATA(1 << ySub) + yLine = y >> SUBPIXEL_SHIFT + r.MaskBuffer[yLine*r.BufferWidth+xp] |= mask + r.WindingBuffer[(yLine*r.BufferWidth+xp)*SUBPIXEL_COUNT+int(ySub)] += winding + x += slope + if (y & SLOPE_FIX_MASK) == 0 { + x += slopeFix; + } } } @@ -159,9 +194,9 @@ func (r *Rasterizer8BitsSample) fillEvenOdd(img *image.RGBA, color *image.RGBACo p := (*uint32)(unsafe.Pointer(&tp[x])) mask ^= r.MaskBuffer[y*uint32(r.BufferWidth)+x] // 8bits - //alpha := uint32(coverageTable[mask]) + alpha := uint32(coverageTable[mask]) // 16bits - alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff]) + //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff]) // 32bits //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff]) @@ -218,23 +253,6 @@ func (r *Rasterizer8BitsSample) RenderNonZeroWinding(img *image.RGBA, color *ima r.fillNonZero(img, color, clipRect) } -//! Adds an edge to be used with non-zero winding fill. -func (r *Rasterizer8BitsSample) addNonZeroEdge(edge *PolygonEdge) { - x := edge.X - slope := edge.Slope - var ySub, mask SUBPIXEL_DATA - var xp, yLine int - winding := NON_ZERO_MASK_DATA_UNIT(edge.Winding) - for y := edge.FirstLine; y <= edge.LastLine; y++ { - ySub = SUBPIXEL_DATA(y & (SUBPIXEL_COUNT - 1)) - xp = (int)(x + SUBPIXEL_OFFSETS[ySub]) - mask = SUBPIXEL_DATA(1 << ySub) - yLine = y >> SUBPIXEL_SHIFT - r.MaskBuffer[yLine*r.BufferWidth+xp] |= mask - r.WindingBuffer[(yLine*r.BufferWidth+xp)*SUBPIXEL_COUNT+int(ySub)] += winding - x += slope - } -} //! Renders the mask to the canvas with non-zero winding fill. func (r *Rasterizer8BitsSample) fillNonZero(img *image.RGBA, color *image.RGBAColor, clipBound [4]float64) { @@ -281,9 +299,9 @@ func (r *Rasterizer8BitsSample) fillNonZero(img *image.RGBA, color *image.RGBACo } // 8bits - //alpha := uint32(coverageTable[mask]) + alpha := uint32(coverageTable[mask]) // 16bits - alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff]) + //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff]) // 32bits //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff]) diff --git a/draw2d/raster/fillerV1/fillerAA.go b/draw2d/raster/fillerV1/fillerAA.go new file mode 100644 index 0000000..6a82549 --- /dev/null +++ b/draw2d/raster/fillerV1/fillerAA.go @@ -0,0 +1,302 @@ +// Copyright 2011 The draw2d Authors. All rights reserved. +// created: 27/05/2011 by Laurent Le Goff +package raster + +import ( + "image" + "unsafe" +) + +const ( + SUBPIXEL_SHIFT = 3 + SUBPIXEL_COUNT = 1 << SUBPIXEL_SHIFT +) + +var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_8 + +type SUBPIXEL_DATA uint16 +type NON_ZERO_MASK_DATA_UNIT uint8 + +type Rasterizer8BitsSample struct { + MaskBuffer []SUBPIXEL_DATA + WindingBuffer []NON_ZERO_MASK_DATA_UNIT + + Width int + BufferWidth int + Height int + ClipBound [4]float64 + RemappingMatrix [6]float64 +} + +/* width and height define the maximum output size for the filler. + * The filler will output to larger bitmaps as well, but the output will + * be cropped. + */ +func NewRasterizer8BitsSample(width, height int) *Rasterizer8BitsSample { + var r Rasterizer8BitsSample + // Scale the coordinates by SUBPIXEL_COUNT in vertical direction + // The sampling point for the sub-pixel is at the top right corner. This + // adjustment moves it to the pixel center. + r.RemappingMatrix = [6]float64{1, 0, 0, SUBPIXEL_COUNT, 0.5 / SUBPIXEL_COUNT, -0.5 * SUBPIXEL_COUNT} + r.Width = width + r.Height = height + // The buffer used for filling needs to be one pixel wider than the bitmap. + // This is because the end flag that turns the fill of is the first pixel + // after the actually drawn edge. + r.BufferWidth = width + 1 + + r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*height) + r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*height*SUBPIXEL_COUNT) + r.ClipBound = clip(0, 0, width, height, SUBPIXEL_COUNT) + return &r +} + +func clip(x, y, width, height, scale int) [4]float64 { + var clipBound [4]float64 + + offset := 0.99 / float64(scale) + + clipBound[0] = float64(x) + offset + clipBound[2] = float64(x+width) - offset + + clipBound[1] = float64(y * scale) + clipBound[3] = float64((y + height) * scale) + return clipBound +} + +func intersect(r1, r2 [4]float64) [4]float64 { + if r1[0] < r2[0] { + r1[0] = r2[0] + } + if r1[2] > r2[2] { + r1[2] = r2[2] + } + if r1[0] > r1[2] { + r1[0] = r1[2] + } + + if r1[1] < r2[1] { + r1[1] = r2[1] + } + if r1[3] > r2[3] { + r1[3] = r2[3] + } + if r1[1] > r1[3] { + r1[1] = r1[3] + } + return r1 +} + +func (r *Rasterizer8BitsSample) RenderEvenOdd(img *image.RGBA, color *image.RGBAColor, polygon *Polygon, tr [6]float64) { + // memset 0 the mask buffer + r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height) + + // inline matrix multiplication + transform := [6]float64{ + tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2], + tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1], + tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2], + tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1], + tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4], + tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5], + } + + clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT) + clipRect = intersect(clipRect, r.ClipBound) + p := 0 + l := len(*polygon) / 2 + var edges [32]PolygonEdge + for p < l { + edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect) + for k := 0; k < edgeCount; k++ { + r.addEvenOddEdge(&(edges[k])) + } + p += 16 + } + + r.fillEvenOdd(img, color, clipRect) +} + +//! Adds an edge to be used with even-odd fill. +func (r *Rasterizer8BitsSample) addEvenOddEdge(edge *PolygonEdge) { + x := edge.X + slope := edge.Slope + var ySub, mask SUBPIXEL_DATA + var xp, yLine int + for y := edge.FirstLine; y <= edge.LastLine; y++ { + ySub = SUBPIXEL_DATA(y & (SUBPIXEL_COUNT - 1)) + xp = (int)(x + SUBPIXEL_OFFSETS[ySub]) + mask = SUBPIXEL_DATA(1 << ySub) + yLine = y >> SUBPIXEL_SHIFT + r.MaskBuffer[yLine*r.BufferWidth+xp] ^= mask + x += slope + } +} + +// Renders the mask to the canvas with even-odd fill. +func (r *Rasterizer8BitsSample) fillEvenOdd(img *image.RGBA, color *image.RGBAColor, clipBound [4]float64) { + var x, y uint32 + + minX := uint32(clipBound[0]) + maxX := uint32(clipBound[2]) + + minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT + maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT + + //pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A) + pixColor := (*uint32)(unsafe.Pointer(color)) + cs1 := *pixColor & 0xff00ff + cs2 := (*pixColor >> 8) & 0xff00ff + + stride := uint32(img.Stride) + var mask SUBPIXEL_DATA + + for y = minY; y < maxY; y++ { + tp := img.Pix[y*stride:] + + mask = 0 + for x = minX; x <= maxX; x++ { + p := (*uint32)(unsafe.Pointer(&tp[x])) + mask ^= r.MaskBuffer[y*uint32(r.BufferWidth)+x] + // 8bits + alpha := uint32(coverageTable[mask]) + // 16bits + //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff]) + // 32bits + //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff]) + + // alpha is in range of 0 to SUBPIXEL_COUNT + invAlpha := uint32(SUBPIXEL_COUNT) - alpha + + ct1 := (*p & 0xff00ff) * invAlpha + ct2 := ((*p >> 8) & 0xff00ff) * invAlpha + + ct1 = ((ct1 + cs1*alpha) >> SUBPIXEL_SHIFT) & 0xff00ff + ct2 = ((ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT)) & 0xff00ff00 + + *p = ct1 + ct2 + } + } +} + +/* + * Renders the polygon with non-zero winding fill. + * param aTarget the target bitmap. + * param aPolygon the polygon to render. + * param aColor the color to be used for rendering. + * param aTransformation the transformation matrix. + */ +func (r *Rasterizer8BitsSample) RenderNonZeroWinding(img *image.RGBA, color *image.RGBAColor, polygon *Polygon, tr [6]float64) { + + r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height) + r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*r.Height*SUBPIXEL_COUNT) + + // inline matrix multiplication + transform := [6]float64{ + tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2], + tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1], + tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2], + tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1], + tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4], + tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5], + } + + clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT) + clipRect = intersect(clipRect, r.ClipBound) + + p := 0 + l := len(*polygon) / 2 + var edges [32]PolygonEdge + for p < l { + edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect) + for k := 0; k < edgeCount; k++ { + r.addNonZeroEdge(&(edges[k])) + } + p += 16 + } + + r.fillNonZero(img, color, clipRect) +} + +//! Adds an edge to be used with non-zero winding fill. +func (r *Rasterizer8BitsSample) addNonZeroEdge(edge *PolygonEdge) { + x := edge.X + slope := edge.Slope + var ySub, mask SUBPIXEL_DATA + var xp, yLine int + winding := NON_ZERO_MASK_DATA_UNIT(edge.Winding) + for y := edge.FirstLine; y <= edge.LastLine; y++ { + ySub = SUBPIXEL_DATA(y & (SUBPIXEL_COUNT - 1)) + xp = (int)(x + SUBPIXEL_OFFSETS[ySub]) + mask = SUBPIXEL_DATA(1 << ySub) + yLine = y >> SUBPIXEL_SHIFT + r.MaskBuffer[yLine*r.BufferWidth+xp] |= mask + r.WindingBuffer[(yLine*r.BufferWidth+xp)*SUBPIXEL_COUNT+int(ySub)] += winding + x += slope + } +} + +//! Renders the mask to the canvas with non-zero winding fill. +func (r *Rasterizer8BitsSample) fillNonZero(img *image.RGBA, color *image.RGBAColor, clipBound [4]float64) { + var x, y uint32 + + minX := uint32(clipBound[0]) + maxX := uint32(clipBound[2]) + + minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT + maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT + + //pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A) + pixColor := (*uint32)(unsafe.Pointer(color)) + cs1 := *pixColor & 0xff00ff + cs2 := (*pixColor >> 8) & 0xff00ff + + stride := uint32(img.Stride) + var mask SUBPIXEL_DATA + var n uint32 + var values [SUBPIXEL_COUNT]NON_ZERO_MASK_DATA_UNIT + for n = 0; n < SUBPIXEL_COUNT; n++ { + values[n] = 0 + } + + for y = minY; y < maxY; y++ { + tp := img.Pix[y*stride:] + + mask = 0 + for x = minX; x <= maxX; x++ { + p := (*uint32)(unsafe.Pointer(&tp[x])) + temp := r.MaskBuffer[y*uint32(r.BufferWidth)+x] + if temp != 0 { + var bit SUBPIXEL_DATA = 1 + for n = 0; n < SUBPIXEL_COUNT; n++ { + if (temp & bit) != 0 { + t := values[n] + values[n] += r.WindingBuffer[(y*uint32(r.BufferWidth)+x)*SUBPIXEL_COUNT+n] + if (t == 0 || values[n] == 0 )&& t != values[n] { + mask ^= bit + } + } + bit <<= 1 + } + } + + // 8bits + alpha := uint32(coverageTable[mask]) + // 16bits + //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff]) + // 32bits + //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff]) + + // alpha is in range of 0 to SUBPIXEL_COUNT + invAlpha := uint32(SUBPIXEL_COUNT) - alpha + + ct1 := (*p & 0xff00ff) * invAlpha + ct2 := ((*p >> 8) & 0xff00ff) * invAlpha + + ct1 = ((ct1 + cs1*alpha) >> SUBPIXEL_SHIFT) & 0xff00ff + ct2 = ((ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT)) & 0xff00ff00 + + *p = ct1 + ct2 + } + } +} diff --git a/draw2d/raster/fixed_point.go b/draw2d/raster/fixed_point.go new file mode 100644 index 0000000..8ed8d38 --- /dev/null +++ b/draw2d/raster/fixed_point.go @@ -0,0 +1,17 @@ +package raster + +type Fix int32 + +const ( + FIXED_SHIFT = 16 + FIXED_FLOAT_COEF = 1 << FIXED_SHIFT +) + +/*! Fixed point math inevitably introduces rounding error to the DDA. The error is + * fixed every now and then by a separate fix value. The defines below set these. + */ +const ( + SLOPE_FIX_SHIFT = 8 + SLOPE_FIX_STEP = 1 << SLOPE_FIX_SHIFT + SLOPE_FIX_MASK = SLOPE_FIX_STEP - 1 +) \ No newline at end of file