Сглаживание кривых с помощью дискретной предварительной фильтрации

66
4

Я ищу реализовать алгоритм кривой безье, описанный в статье "Сглаживание кривых дискретной предварительной фильтрацией" А. Е. Фабриса и А. Р. Форреста. Однако мне не хватает основной части головоломки: алгоритм сдерживания точек для кривых Corthout и Pol. Об этом говорится в книге "Растровая визуализация и цифровая типография".

Я мог просто перебрать каждый пиксель, вычислить кратчайшее расстояние до безье и использовать его для вычисления эффекта кисти. Однако, как упоминалось в статье, это неэффективный подход.

Есть ли схема или псевдокод для алгоритма сдерживания точки (или эквивалента), который будет делать то же самое?

спросил(а) 2013-04-05T05:59:00+04:00 7 лет, 3 месяца назад
1
Решение
55

Я собираюсь ответить на свой вопрос, увидев, что я это понял. Оказалось, что сдерживание точки не требуется, и было указано в статье.

Здесь реализована реализация в python и wxwidgets, создавая несколько кривых с помощью кисти.

К сожалению, это слишком медленно для практического использования, даже после перезаписи на C и проведения нескольких оптимизаций. Лучшее, что я мог получить, было около 30 мс за безье, и для рендеринга 100 кривых потребовалось 3 секунды. Большинство других векторных приложений, как правило, используют алгоритмы рендеринга bezier на основе пути, которые я сейчас изучаю.

#!/usr/bin/python
# test15.py

import wx
import math
import cProfile

class Brush(object):
def __init__(self, size):
self._brushsize = size
self.__halfbrush = self._brushsize / 2
# returns true if inside or touching box
def insidebox(self, box):
minp = box[0]
maxp = box[1]
if maxp.x < -self.__halfbrush:
return False
if minp.x > self.__halfbrush:
return False
if maxp.y < -self.__halfbrush:
return False
if minp.y > self.__halfbrush:
return False
return True

def at(self, p):
px = p.x * p.x # 0.027
py = p.y * p.y
d = math.sqrt(px + py)
return d < self.__halfbrush

def getSize(self):
return self._brushsize

class BezierPoint(object):
SCALEFACTOR = 4 # we divide each pixel into 4x4 matrix

@classmethod
def newRealPoint(nbs, x,y):
x=int(x*BezierPoint.SCALEFACTOR)
y=int(y*BezierPoint.SCALEFACTOR)
return BezierPoint(x,y)

def __init__(self,x,y):
self.x=int(x)
self.y=int(y)

def __str__(self):
return self.x+ 'x' + self.y

#recalls = 0

class QuadBezier(object): # cubic bezier
COVERED = 0x01
NOT_COVERED = 0x02

@classmethod
def newRealPoint(nbs, x1,y1,x2,y2,x3,y3,x4,y4):
b = QuadBezier(x1,y1,x2,y2,x3,y3,x4,y4)
b.p1 = BezierPoint.newRealPoint( x1, y1)
b.p2 = BezierPoint.newRealPoint( x2, y2)
b.p3 = BezierPoint.newRealPoint( x3, y3)
b.p4 = BezierPoint.newRealPoint( x4, y4)
b._minx = min(b.p1.x,b.p2.x,b.p3.x,b.p4.x)
b._maxx = max(b.p1.x,b.p2.x,b.p3.x,b.p4.x)
b._miny = min(b.p1.y,b.p2.y,b.p3.y,b.p4.y)
b._maxy = max(b.p1.y,b.p2.y,b.p3.y,b.p4.y)
return b

def __init__(self,x1,y1,x2,y2,x3,y3,x4,y4):
self.p1 = BezierPoint(x1,y1)
self.p2 = BezierPoint(x2,y2)
self.p3 = BezierPoint(x3,y3)
self.p4 = BezierPoint(x4,y4)
self._minx = min(x1,x2,x3,x4)
self._maxx = max(x1,x2,x3,x4)
self._miny = min(y1,y2,y3,y4)
self._maxy = max(y1,y2,y3,y4)

def __str__(self):
s = 'p1 = ' + str(self.p1.x) +'x'+ str(self.p1.y) +' p2 = '+str(self.p2.x) +'x'+str(self.p2.y)
s = s + ' p3 = ' + str(self.p3.x) +'x'+ str(self.p3.y) +' p4 = '+str(self.p4.x) +'x'+str(self.p4.y)
return s

def subdivide(self, left=True):
# http://antigrain.com/research/adaptive_bezier/
# according to the paper, we control vertices need to be performed at
# higher precision than the discrete space will allow to for any accuracy loss
x1 = self.p1.x << 2
x2 = self.p2.x << 2
x3 = self.p3.x << 2
x4 = self.p4.x << 2
y1 = self.p1.y << 2
y2 = self.p2.y << 2
y3 = self.p3.y << 2
y4 = self.p4.y << 2
x12 = (x1 + x2) >> 1
y12 = (y1 + y2) >> 1
x23 = (x2 + x3) >> 1
y23 = (y2 + y3) >> 1
x34 = (x3 + x4) >> 1
y34 = (y3 + y4) >> 1
x123 = (x12 + x23) >> 1
y123 = (y12 + y23) >> 1
x234 = (x23 + x34) >> 1
y234 = (y23 + y34) >> 1
x1234 = (x123 + x234) >> 1
y1234 = (y123 + y234) >> 1

if left:
return QuadBezier(x1 >> 2, y1 >> 2, x12 >> 2, y12 >> 2, x123 >> 2, y123 >> 2, x1234 >> 2, y1234 >> 2)
return QuadBezier(x1234 >> 2, y1234 >> 2, x234 >> 2, y234 >> 2, x34 >> 2, y34 >> 2, x4 >> 2, y4 >> 2)

def getboundingbox(self):
return BezierPoint(self._minx,self._miny), BezierPoint(self._maxx,self._maxy)

def area (self):
m1,m2 = self.getboundingbox()
return m2.x-m1.x, m2.y-m1.y

def minus (self, p):
px = p.x
py = p.y
return QuadBezier(self.p1.x - px, self.p1.y - py,
self.p2.x - px, self.p2.y - py,
self.p3.x - px, self.p3.y - py,
self.p4.x - px, self.p4.y - py)

def draw(self, img,pd, stackofbrushes):
mmin,mmax = self.getboundingbox()
numberofbrushes = len(stackofbrushes)
largestbrushsize = stackofbrushes[numberofbrushes-1].getSize()
print 'largestbrushsize = ', largestbrushsize
xstart,xend = int(math.floor(mmin.x/BezierPoint.SCALEFACTOR-largestbrushsize)), int(math.ceil(mmax.x/BezierPoint.SCALEFACTOR+largestbrushsize))
ystart,yend = int(math.floor(mmin.y/BezierPoint.SCALEFACTOR-largestbrushsize)), int(math.ceil(mmax.y/BezierPoint.SCALEFACTOR+largestbrushsize))
print (xend - xstart) * 4, (yend - ystart) * 4, ' total area = ', ((xend - xstart) * 4) * ((yend - ystart) * 4)
# for each pixel in the image
for x in xrange(xstart, xend):
for y in xrange(ystart, yend):
pixel = BezierPoint(x*BezierPoint.SCALEFACTOR+2,y*BezierPoint.SCALEFACTOR+2) # todo: fix me
covered, height = self.antialisedDilate(stackofbrushes, pixel, numberofbrushes)
if covered == QuadBezier.COVERED:
img.MoveTo(pd,x,y)
#print height
img.Set(41*height,41*height,41*height)

def antialisedDilate(self, stackofbrushes, p, numberofbrushes):
mstroke = self.minus(p) # centre the stroke
h = 0
n = numberofbrushes - 1
for i in range(n,-1,-1):
if mstroke.stroke(stackofbrushes[i]) == QuadBezier.COVERED:
h = i
else:
if h == 0:
return QuadBezier.NOT_COVERED, 0
return QuadBezier.COVERED, h
return QuadBezier.COVERED, h

def baseCase(self):
""" returns true if the curve is primitive, that is that the length of the curve is <= 1 of the discrete space selected
"""
p1,p2 = self.getboundingbox()
mx,my = p2.x-p1.x, p2.y-p1.y
#print mx,my
distance = 2
if mx >= -distance and mx <= distance:
if my > -distance and my <= distance:
return True
return False

def stroke(self, b):
""" http://pdf.aminer.org/000/593/297/antialiasing_of_curves_by_discrete_pre_filtering.pdf
"""
# NotInDilatedBBox
if not b.insidebox(self.getboundingbox()):
return QuadBezier.NOT_COVERED
if self.baseCase():
return QuadBezier.NOT_COVERED
if b.at(self.p1):
return QuadBezier.COVERED
if b.at(self.p4):
return QuadBezier.COVERED

left = self.subdivide(True)
if (left.stroke(b)==QuadBezier.COVERED):
return QuadBezier.COVERED
right = self.subdivide(False)
if (right.stroke(b)==QuadBezier.COVERED):
return QuadBezier.COVERED

return QuadBezier.NOT_COVERED

pd = None
pixels = None
def timer ():
stackofbrushes = [Brush(6.0), Brush(7.0), Brush(9.0), Brush(11.0), Brush(13.0), Brush(15.0)] # largest last

for i in xrange(6):
yplus = i * 20
bezier = QuadBezier.newRealPoint(40.0,50.0+yplus, 80.0,46.0+yplus, 120.0,46.0+yplus, 160.0,50.0+yplus)
bezier.draw(pixels, pd, stackofbrushes)

class MyToolBar(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(700, 700))
self.Centre()

self.Bind(wx.EVT_TOOL, self.OnExit, id=4)
bmp = wx.EmptyBitmap(700,700)
# we're using the undocumented native pixel data because the memorydx object introduces artifacts on MacOS
global pd
global pixels

pd = wx.NativePixelData(bmp, wx.Point(0,0), wx.Size(700,700))
width,height = pd.GetWidth(), pd.GetHeight()
pixels = pd.GetPixels()
for x in xrange(width):
for y in xrange(height):
pixels.MoveTo(pd,x,y)
pixels.Set(255,255,255)

cProfile.run('timer()')

wx.StaticBitmap(self, wx.ID_ANY, bmp)

def OnExit(self, event):
self.Close()

class MyApp(wx.App):
def OnInit(self):
frame = MyToolBar(None, -1, 'Antialiasing of Curves by Discrete Pre-filtering')
frame.Show(True)
return True

app = MyApp(0)
app.MainLoop()

ответил(а) 2013-04-28T11:27:00+04:00 7 лет, 2 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема