├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── contour ├── CubicBezier.py ├── Line.py ├── QuadraticBezier.py └── __init__.py ├── rasterizer.py ├── test.py └── util ├── __init__.py ├── getpx.cpp ├── getpx.dll ├── getpx.py ├── getpx_compile.cmd ├── solver.cpp ├── solver.dll ├── solver.py └── solver_compile.cmd /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############# 2 | ## Python 3 | ############# 4 | 5 | *.py[co] 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist/ 11 | build/ 12 | eggs/ 13 | parts/ 14 | var/ 15 | sdist/ 16 | develop-eggs/ 17 | .installed.cfg 18 | 19 | # Installer logs 20 | pip-log.txt 21 | 22 | # Unit test / coverage reports 23 | .coverage 24 | .tox 25 | 26 | #Translations 27 | *.mo 28 | 29 | #Mr Developer 30 | .mr.developer.cfg 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ming 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Wavelet Rasterization 2 | ===================== 3 | Introduction 4 | ------------ 5 | Wavelet rasterization is a method for analytically calculating an anti-aliased rasterization of arbitrary polygons or shape bounded by Bezier curves. For more details, please read the following paper: 6 | 7 | Manson, Josiah, and Scott Schaefer. **"Wavelet rasterization."** Computer Graphics Forum. Vol. 30. No. 2. Blackwell Publishing Ltd, 2011. 8 | 9 | This is a python implementation of the algorithm. Currently it supports three types of contours: 10 | * Polygon 11 | * Quadratic Bezier Contour 12 | * Cubic Bezier Contour 13 | 14 | Usage 15 | ----- 16 | Rasterizing these contours are very simple: 17 | 18 | 1. create a specific `Contour` object (`Line.Contour`, `QuadraticBezier.Contour`, or `CubicBezier.Contour`) 19 | 2. use this contour to construct a `Rasterizer` object 20 | 3. call the method `get()` of the `Rasterizer` object, you get an array of pixels, each of which has a value range from 0 to 1 that indicates the local transparent of the shape. 21 | 22 | Example 23 | ------- 24 | An example session could like: 25 | 26 | import cv2, numpy as np # for image IO 27 | from contour import * 28 | from rasterizer import Rasterizer 29 | 30 | ## rasterize a polyline 31 | contour = Line.Contour([(4,4), (30,6), (10,14)]) 32 | raster = Rasterizer(contour, 32, 32).get() 33 | raster = np.array(np.asarray(raster)*255+0.5, np.uint8) 34 | cv2.imwrite('var/Line.png', raster) 35 | 36 | Assuming everything is working OK, the examples should generate the following image: 37 | 38 | ![line](https://f.cloud.github.com/assets/2270240/566814/829ae7dc-c6a0-11e2-91ee-45184f5a8a1d.png) 39 | 40 | You can also rasterize a quadratic bezier contour: 41 | 42 | contour = QuadraticBezier.Contour([(4,4), (28,4), (28,28), (4,28)]) 43 | raster = Rasterizer(contour, 32, 32).get() 44 | raster = np.array(np.asarray(raster)*255+0.5, np.uint8) 45 | cv2.imwrite('var/QuadraticBezier.png', raster) 46 | 47 | ![quadraticbezier](https://f.cloud.github.com/assets/2270240/566816/8646ba6e-c6a0-11e2-9cd0-19cd058768b8.png) 48 | 49 | or a cubic bezier contour: 50 | 51 | ## rasterize a cubic bezier contour 52 | contour = CubicBezier.Contour([(4,4),(12,4),(28,12),(28,28),(12,28),(4,12)]) 53 | raster = Rasterizer(contour, 32, 32).get() 54 | raster = np.array(np.asarray(raster)*255+0.5, np.uint8) 55 | cv2.imwrite('var/CubicBezier.png', raster) 56 | 57 | ![cubicbezier](https://f.cloud.github.com/assets/2270240/566823/a0f4b2d0-c6a0-11e2-8b89-e1045d573714.png) 58 | -------------------------------------------------------------------------------- /contour/CubicBezier.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from collections import namedtuple 3 | from util.solver import cubic 4 | 5 | # ----------------------------------------------------------------------------- 6 | Point = namedtuple('Point', 'x y') 7 | # ----------------------------------------------------------------------------- 8 | 9 | class CubicBezier: 10 | 11 | def __init__(self, x0, y0, x1, y1, x2, y2, x3, y3): 12 | self.x0, self.y0, self.x1, self.y1 = x0, y0, x1, y1 13 | self.x2, self.y2, self.x3, self.y3 = x2, y2, x3, y3 14 | 15 | def evaluate(self, t): 16 | return (self.x0*(1-t)**3 + 3*self.x1*(1-t)**2*t \ 17 | + 3*self.x2*(1-t)*t**2 + self.x3*t**3, 18 | self.y0*(1-t)**3 + 3*self.y1*(1-t)**2*t \ 19 | + 3*self.y2*(1-t)*t**2 + self.y3*t**3) 20 | 21 | def subsection(self, t0, t1): 22 | u0 = 1.0 - t0 23 | u1 = 1.0 - t1 24 | 25 | qxa = self.x0*u0*u0 + self.x1*2*t0*u0 + self.x2*t0*t0 26 | qxb = self.x0*u1*u1 + self.x1*2*t1*u1 + self.x2*t1*t1 27 | qxc = self.x1*u0*u0 + self.x2*2*t0*u0 + self.x3*t0*t0 28 | qxd = self.x1*u1*u1 + self.x2*2*t1*u1 + self.x3*t1*t1 29 | 30 | qya = self.y0*u0*u0 + self.y1*2*t0*u0 + self.y2*t0*t0 31 | qyb = self.y0*u1*u1 + self.y1*2*t1*u1 + self.y2*t1*t1 32 | qyc = self.y1*u0*u0 + self.y2*2*t0*u0 + self.y3*t0*t0 33 | qyd = self.y1*u1*u1 + self.y2*2*t1*u1 + self.y3*t1*t1 34 | 35 | sec = CubicBezier( qxa*u0 + qxc*t0, qya*u0 + qyc*t0, 36 | qxa*u1 + qxc*t1, qya*u1 + qyc*t1, 37 | qxb*u0 + qxd*t0, qyb*u0 + qyd*t0, 38 | qxb*u1 + qxd*t1, qyb*u1 + qyd*t1 ) 39 | return sec 40 | 41 | def clip(self, left, right, bottom, top): 42 | def is_t_in(t, eps = 1e-5): 43 | pt = self.evaluate(t) 44 | return left-eps<=pt[0]<=right+eps and top-eps<=pt[1]<=bottom+eps 45 | 46 | ax = -self.x0 + 3*self.x1 - 3*self.x2 + self.x3 47 | bx = 3*self.x0 - 6*self.x1 + 3*self.x2 48 | cx, _dx = 3*self.x1 - 3*self.x0, self.x0 49 | ay = -self.y0 + 3*self.y1 - 3*self.y2 + self.y3 50 | by = 3*self.y0 - 6*self.y1 + 3*self.y2 51 | cy, _dy = 3*self.y1 - 3*self.y0, self.y0 52 | ts = [0] 53 | ts += cubic(ax, bx, cx, _dx-left) 54 | ts += cubic(ax, bx, cx, _dx-right) 55 | ts += cubic(ay, by, cy, _dy-bottom) 56 | ts += cubic(ay, by, cy, _dy-top) 57 | ts.append(1) 58 | ts = [t for t in ts if 0 <= t <= 1 and is_t_in(t)] 59 | ts = sorted(ts) 60 | ts = [t for i, t in enumerate(ts) if t != ts[i-1]] 61 | pairs = [(ts[i-1], t) for i, t in enumerate(ts) \ 62 | if i > 0 and is_t_in((t + ts[i-1]) * 0.5)] 63 | sections = [self.subsection(a, b) for a, b in pairs] 64 | return sections 65 | 66 | def get_KL(self, eps = 1e-5): 67 | Kx, Ky, Lx, Ly = 0, 0, 0, 0 68 | for sec in self.clip(0, 1, 1, 0): 69 | v3 = Point(sec.x0, sec.y0) 70 | v2 = Point(sec.x1, sec.y1) 71 | v1 = Point(sec.x2, sec.y2) 72 | v0 = Point(sec.x3, sec.y3) 73 | if abs(v0.x - 1) < eps and abs(v1.x - 1) < eps \ 74 | and abs(v2.x - 1) < eps and abs(v3.x - 1) < eps\ 75 | or abs(v0.y - 1) < eps and abs(v1.y - 1) < eps \ 76 | and abs(v2.y - 1) < eps and abs(v3.y - 1) < eps: 77 | continue 78 | 79 | Kx += 1./4 * (v0.y - v3.y) 80 | Ky += 1./4 * (v3.x - v0.x) 81 | Lx += 1./80* (6 * v2.y*v3.x + 3 * v1.y*(v2.x+v3.x) \ 82 | + v0.y * (6*v1.x+3*v2.x+v3.x) \ 83 | - 6 * v2.x*v3.y - 3 * v1.x*(v2.y+v3.y) \ 84 | - 10 * v3.x*v3.y \ 85 | + v0.x * (10*v0.y-6*v1.y-3*v2.y-v3.y) ) 86 | Ly += 1./80* (6 * v2.y*v3.x + 3 * v1.y*(v2.x+v3.x) \ 87 | + v0.y * (6*v1.x+3*v2.x+v3.x) \ 88 | - 6 * v2.x*v3.y - 3 * v1.x*(v2.y+v3.y) \ 89 | + 10 * v3.x*v3.y \ 90 | - v0.x * (10*v0.y+6*v1.y+3*v2.y+v3.y) ) 91 | return Point(Kx, Ky), Point(Lx, Ly) 92 | 93 | # ----------------------------------------------------------------------------- 94 | 95 | class Contour: 96 | 97 | def __init__(self, contour): 98 | self.contour = contour 99 | 100 | def __str__(self): 101 | info = ' '.join('(%2.1f, %2.1f)' % (s[0], s[1]) for s in self.contour) 102 | return ' :\t'.join(['CubicBezier', info]) 103 | 104 | def process(self, method): 105 | self.contour = [method(p) for p in self.contour] 106 | 107 | def area(self): 108 | def det(a, b): return a[0] * b[1] - a[1] * b[0] 109 | s = 0 110 | for v0, v1, v2, v3 in self.each(): 111 | s += 3./10 * det(v0,v1) + 3./20 * det(v1,v2) + 3./10 * det(v2,v3) \ 112 | + 3./20 * det(v0,v2) + 3./20 * det(v1,v3) + 1./20 * det(v0,v3) 113 | return s 114 | 115 | def each(self): 116 | for i in xrange(0, len(self.contour), 3): 117 | v3 = self.contour[i] 118 | v2 = self.contour[i-1] 119 | v1 = self.contour[i-2] 120 | v0 = self.contour[i-3] 121 | yield v0, v1, v2, v3 122 | 123 | def to_lines(self): 124 | tts = np.linspace(0,1, num=50) 125 | for section in self.each(): 126 | section = (s[i] for s in section for i in xrange(2)) 127 | bezier = CubicBezier(*section) 128 | for start, end in zip(tts[:-1], tts[1:]): 129 | sx, sy = bezier.evaluate(start) 130 | ex, ey = bezier.evaluate(end) 131 | yield sx, sy, ex, ey 132 | 133 | def get_KL(self, section): 134 | bezier = CubicBezier(*section) 135 | return bezier.get_KL() 136 | 137 | # ----------------------------------------------------------------------------- -------------------------------------------------------------------------------- /contour/Line.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from collections import namedtuple 3 | 4 | # ----------------------------------------------------------------------------- 5 | Point = namedtuple('Point', 'x y') 6 | # ----------------------------------------------------------------------------- 7 | 8 | class Line: 9 | 10 | def __init__(self, x0, y0, x1, y1): 11 | self.x0, self.y0, self.x1, self.y1 = x0, y0, x1, y1 12 | 13 | def clip(self, left, right, bottom, top): 14 | t0, t1 = 0, 1 15 | xdelta = self.x1 - self.x0 16 | ydelta = self.y1 - self.y0 17 | for edge in xrange(4):#traverse through left, right, bottom, top edges. 18 | if edge == 0: p, q = -xdelta, -(left-self.x0) 19 | elif edge == 1: p, q = xdelta, (right-self.x0) 20 | elif edge == 2: p, q = ydelta, (bottom-self.y0) 21 | elif edge == 3: p, q = -ydelta, -(top-self.y0) 22 | if p == 0 and q < 0: return [] 23 | if p < 0: 24 | r = q / float(p) 25 | if r > t1: return [] 26 | elif r > t0: t0 = r # line is clipped! 27 | elif p > 0: 28 | r = q / float(p) 29 | if r < t0: return [] 30 | elif r < t1: t1 = r # line is clipped! 31 | clipped_line = Line(self.x0 + t0*xdelta, self.y0 + t0*ydelta, 32 | self.x0 + t1*xdelta, self.y0 + t1*ydelta) 33 | return [clipped_line] 34 | 35 | def get_KL(self, eps = 1e-5): 36 | Kx, Ky, Lx, Ly = 0, 0, 0, 0 37 | for sec in self.clip(0, 1, 1, 0): 38 | v1 = Point(sec.x0, sec.y0) 39 | v0 = Point(sec.x1, sec.y1) 40 | if abs(v0.x - 1) < eps and abs(v1.x - 1) < eps \ 41 | or abs(v0.y - 1) < eps and abs(v1.y - 1) < eps: 42 | continue 43 | 44 | Kx += 1./4 * (v0.y - v1.y) 45 | Ky += 1./4 * (v1.x - v0.x) 46 | Lx += 1./8 * (v0.y-v1.y) * (v0.x+v1.x) 47 | Ly += 1./8 * (v1.x-v0.x) * (v0.y+v1.y) 48 | return Point(Kx, Ky), Point(Lx, Ly) 49 | 50 | # ----------------------------------------------------------------------------- 51 | 52 | class Contour: 53 | 54 | def __init__(self, contour): 55 | self.contour = contour 56 | 57 | def __str__(self): 58 | info = ' '.join('(%2.1f, %2.1f)' % (s[0], s[1]) for s in self.contour) 59 | return ' :\t'.join(['Line', info]) 60 | 61 | def process(self, method): 62 | self.contour = [method(p) for p in self.contour] 63 | 64 | def area(self): 65 | def det(a, b): return a[0] * b[1] - a[1] * b[0] 66 | return 0.5 * sum(det(a, b) for (a, b) in \ 67 | zip(self.contour, self.contour[1:] + [self.contour[0]])) 68 | 69 | def each(self): 70 | for i, v1 in enumerate(self.contour): 71 | v0 = self.contour[i-1] 72 | yield v0, v1 73 | 74 | def to_lines(self): 75 | for v0, v1 in self.each(): 76 | yield v0[0], v0[1], v1[0], v1[1] 77 | 78 | def get_KL(self, section): 79 | line = Line(*section) 80 | return line.get_KL() 81 | 82 | # ----------------------------------------------------------------------------- -------------------------------------------------------------------------------- /contour/QuadraticBezier.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from collections import namedtuple 3 | from util.solver import quadric 4 | 5 | # ----------------------------------------------------------------------------- 6 | Point = namedtuple('Point', 'x y') 7 | # ----------------------------------------------------------------------------- 8 | 9 | class QuadraticBezier: 10 | 11 | def __init__(self, x0, y0, x1, y1, x2, y2): 12 | self.x0, self.y0, self.x1, self.y1 = x0, y0, x1, y1 13 | self.x2, self.y2 = x2, y2 14 | 15 | def evaluate(self, t): 16 | return (self.x0*(1-t)**2 + 2*self.x1*(1-t)*t + self.x2*t**2, 17 | self.y0*(1-t)**2 + 2*self.y1*(1-t)*t + self.y2*t**2) 18 | 19 | def subsection(self, t0, t1): 20 | x0, y0 = self.evaluate(t0) 21 | x2, y2 = self.evaluate(t1) 22 | u0 = 1.0 - t0 23 | xm = u0 * self.x1 + t0 * self.x2 24 | ym = u0 * self.y1 + t0 * self.y2 25 | t = (t1 - t0) / u0 26 | x1 = (1-t) * x0 + t * xm 27 | y1 = (1-t) * y0 + t * ym 28 | 29 | sec = QuadraticBezier( x0, y0, x1, y1, x2, y2 ) 30 | return sec 31 | 32 | def clip(self, left, right, bottom, top): 33 | def is_t_in(t, eps = 1e-5): 34 | pt = self.evaluate(t) 35 | return left-eps<=pt[0]<=right+eps and top-eps<=pt[1]<=bottom+eps 36 | 37 | ax = self.x0 - 2*self.x1 + self.x2 38 | bx = -2*self.x0 + 2*self.x1 39 | _cx = self.x0 40 | ay = self.y0 - 2*self.y1 + self.y2 41 | by = -2*self.y0 + 2*self.y1 42 | _cy = self.y0 43 | ts = [0] 44 | ts += quadric(ax, bx, _cx-left) 45 | ts += quadric(ax, bx, _cx-right) 46 | ts += quadric(ay, by, _cy-bottom) 47 | ts += quadric(ay, by, _cy-top) 48 | ts.append(1) 49 | ts = [t for t in ts if 0 <= t <= 1 and is_t_in(t)] 50 | ts = sorted(ts) 51 | ts = [t for i, t in enumerate(ts) if t != ts[i-1]] 52 | pairs = [(ts[i-1], t) for i, t in enumerate(ts) \ 53 | if i > 0 and is_t_in((t + ts[i-1]) * 0.5)] 54 | sections = [self.subsection(a, b) for a, b in pairs] 55 | return sections 56 | 57 | def get_KL(self, eps = 1e-5): 58 | Kx, Ky, Lx, Ly = 0, 0, 0, 0 59 | for sec in self.clip(0, 1, 1, 0): 60 | v2 = Point(sec.x0, sec.y0) 61 | v1 = Point(sec.x1, sec.y1) 62 | v0 = Point(sec.x2, sec.y2) 63 | if abs(v0.x-1) < eps and abs(v1.x-1) < eps and abs(v2.x-1) < eps\ 64 | or abs(v0.y-1) < eps and abs(v1.y-1) < eps and abs(v2.y-1) < eps: 65 | continue 66 | 67 | Kx += 1./4 * (v0.y - v2.y) 68 | Ky += 1./4 * (v2.x - v0.x) 69 | Lx += 1./24* (3 * v0.x*v0.y + 2 * v0.y*v1.x - 2 * v0.x*v1.y \ 70 | + v0.y*v2.x + 2 * v1.y*v2.x -(v0.x+2*v1.x+3*v2.x)*v2.y) 71 | Ly += 1./24* (2 * v1.y*v2.x + v0.y * (2*v1.x+v2.x) - 2 * v1.x*v2.y\ 72 | + 3 * v2.x*v2.y - v0.x * (3*v0.y+2*v1.y+v2.y)) 73 | return Point(Kx, Ky), Point(Lx, Ly) 74 | 75 | # ----------------------------------------------------------------------------- 76 | 77 | class Contour: 78 | 79 | def __init__(self, contour): 80 | self.contour = contour 81 | 82 | def __str__(self): 83 | info = ' '.join('(%2.1f, %2.1f)' % (s[0], s[1]) for s in self.contour) 84 | return ' :\t'.join(['QuadraticBezier', info]) 85 | 86 | def process(self, method): 87 | self.contour = [method(p) for p in self.contour] 88 | 89 | def area(self): 90 | def det(a, b): return a[0] * b[1] - a[1] * b[0] 91 | s = 0 92 | for v0, v1, v2 in self.each(): 93 | s += 1./3 * det(v0,v1) + 1./3 * det(v1,v2) + 1./6 * det(v0,v2) 94 | return s 95 | 96 | def each(self): 97 | for i in xrange(0, len(self.contour), 2): 98 | v2 = self.contour[i] 99 | v1 = self.contour[i-1] 100 | v0 = self.contour[i-2] 101 | yield v0, v1, v2 102 | 103 | def to_lines(self): 104 | tts = np.linspace(0,1, num=50) 105 | for section in self.each(): 106 | section = (s[i] for s in section for i in xrange(2)) 107 | bezier = QuadraticBezier(*section) 108 | for start, end in zip(tts[:-1], tts[1:]): 109 | sx, sy = bezier.evaluate(start) 110 | ex, ey = bezier.evaluate(end) 111 | yield sx, sy, ex, ey 112 | 113 | def get_KL(self, section): 114 | bezier = QuadraticBezier(*section) 115 | return bezier.get_KL() 116 | 117 | # ----------------------------------------------------------------------------- -------------------------------------------------------------------------------- /contour/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Line', 'QuadraticBezier', 'CubicBezier'] -------------------------------------------------------------------------------- /rasterizer.py: -------------------------------------------------------------------------------- 1 | import math, copy 2 | from collections import namedtuple 3 | 4 | # ----------------------------------------------------------------------------- 5 | Point = namedtuple('Point', 'x y') 6 | # ----------------------------------------------------------------------------- 7 | 8 | class Rasterizer: 9 | 10 | def __init__(self, contour, w, h): 11 | self.w = w 12 | self.h = h 13 | self.max_j = int(math.ceil(math.log(max(w,h), 2)))-1 14 | self.wh = 2 ** (self.max_j+1) 15 | def normalize(p): return (p[0]/float(self.wh), p[1]/float(self.wh)) 16 | self.contour = copy.deepcopy(contour) 17 | self.contour.process(normalize) 18 | self.area = self.contour.area() 19 | self.lattice = [Point(*normalize((x,y))) \ 20 | for x in xrange(h) for y in xrange(w)] 21 | # prepare all c 22 | self.all_c = {} 23 | for j in xrange(self.max_j+1): 24 | for kx in xrange(2**j): 25 | for ky in xrange(2**j): 26 | self.all_c[(j, kx,ky)] = self.c(j, (kx,ky)) 27 | 28 | def psi(self, p, e, j, k): 29 | def psi_1d(p, e): 30 | if e == 0: return 1 if 0 <= p < 1 else 0 31 | else: return (1 if 0<=p<0.5 else -1) if 0 <= p < 1 else 0 32 | return 2**j * psi_1d(2**j*p.x-k.x, e.x) * psi_1d(2**j*p.y-k.y, e.y) 33 | 34 | def c(self, j, k): 35 | def transform(section, Q): 36 | return (2**(j+1)*p[i]-k[i]*2-Q[i] \ 37 | for p in section for i in xrange(2)) 38 | Q_00, Q_01 = Point(0, 0), Point(0, 1) 39 | Q_10, Q_11 = Point(1, 0), Point(1, 1) 40 | c10, c01, c11 = 0, 0, 0 41 | for section in self.contour.each(): 42 | KQ00, LQ00 = self.contour.get_KL(transform(section, Q_00)) 43 | KQ01, LQ01 = self.contour.get_KL(transform(section, Q_01)) 44 | KQ10, LQ10 = self.contour.get_KL(transform(section, Q_10)) 45 | KQ11, LQ11 = self.contour.get_KL(transform(section, Q_11)) 46 | c10 += LQ00.x + LQ01.x + KQ10.x \ 47 | - LQ10.x + KQ11.x - LQ11.x 48 | c01 += LQ00.y + LQ10.y + KQ01.y \ 49 | - LQ01.y + KQ11.y - LQ11.y 50 | c11 += LQ00.x - LQ01.x + KQ10.x \ 51 | - LQ10.x - KQ11.x + LQ11.x 52 | return c01, c10, c11 53 | 54 | def g(self, p): 55 | s = self.area 56 | E = [Point(0,1), Point(1,0), Point(1,1)] 57 | for j in xrange(self.max_j+1): 58 | for kx in xrange(2**j): 59 | for ky in xrange(2**j): 60 | k = Point(kx, ky) 61 | cs = self.all_c[(j, kx,ky)] 62 | for i, e in enumerate(E): 63 | psi = self.psi(p, e, j, k) 64 | if psi > 0: s += cs[i] 65 | elif psi < 0: s -= cs[i] 66 | return s 67 | 68 | def get(self): 69 | px_arr = [self.g(p) for p in self.lattice] 70 | px_mat = [px_arr[i*self.w : (i+1)*self.w] for i in xrange(self.h)] 71 | return px_mat 72 | 73 | def get_fast(self): # 100x faster than get() 74 | from util.getpx import get_px as get_cpp 75 | px_arr = get_cpp(self.area, self.max_j, self.all_c, self.lattice) 76 | px_mat = [px_arr[i*self.w : (i+1)*self.w] for i in xrange(self.h)] 77 | return px_mat 78 | 79 | # ----------------------------------------------------------------------------- 80 | 81 | if __name__ == '__main__': 82 | import cv2, numpy as np,time 83 | from contour import * 84 | 85 | ts = time.time() 86 | contour= Line.Contour([(8,8), (60,12), (20,28)]) 87 | raster = Rasterizer(contour, 64, 64).get_fast() 88 | raster = np.array(np.asarray(raster)*255+0.5, np.uint8) 89 | cv2.imwrite('var/Line.png', raster) 90 | 91 | contour= QuadraticBezier.Contour([(8,8), (56,8), (56,56), (8,56)]) 92 | raster = Rasterizer(contour, 64, 64).get_fast() 93 | raster = np.array(np.asarray(raster)*255+0.5, np.uint8) 94 | cv2.imwrite('var/QuadraticBezier.png', raster) 95 | 96 | contour= CubicBezier.Contour([(8,8),(12,8),(56,24),(56,56),(24,56),(8,24)]) 97 | raster = Rasterizer(contour, 64, 64).get_fast() 98 | raster = np.array(np.asarray(raster)*255+0.5, np.uint8) 99 | cv2.imwrite('var/CubicBezier.png', raster) 100 | print time.time() - ts -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import cv2, time, numpy as np 2 | from random import randint, choice 3 | from rasterizer import Rasterizer 4 | from contour import * 5 | 6 | if __name__ == '__main__': 7 | 8 | def single_test(tp, n, w, h, z, channel): 9 | contour = tp.Contour([(randint(1,h-1), randint(1,w-1)) \ 10 | for i in xrange(n)]) 11 | if contour.area() < 0: return None 12 | 13 | ts = time.time() 14 | raster = Rasterizer(contour, w, h).get() 15 | print '%s\ttime: %2.1fs' % (contour, time.time()-ts) 16 | 17 | raster = np.array(np.asarray(raster)*255+0.5, np.uint8) 18 | raster = cv2.resize(raster, (w*z,h*z), interpolation=cv2.INTER_NEAREST) 19 | img = np.zeros((h*z,w*z,3), np.uint8) 20 | img[:,:,channel] = raster 21 | line_color = [0 if channel == ch else 255 for ch in xrange(3)] 22 | for sx, sy, ex, ey in contour.to_lines(): 23 | cv2.line(img, (int(sy*z),int(sx*z)), (int(ey*z),int(ex*z)), 24 | line_color, 2, cv2.CV_AA) 25 | return img 26 | 27 | w, h, z = 17, 13, 50 28 | params = [ (Line, 3, w, h, z, 0), 29 | (QuadraticBezier, 4, w, h, z, 1), 30 | (CubicBezier, 3, w, h, z, 2) ] 31 | while True: 32 | vis_img = single_test(*choice(params)) 33 | if vis_img is None: continue 34 | cv2.namedWindow('raster') 35 | cv2.imshow('raster', vis_img) 36 | cv2.waitKey(1) -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufoym/wavelet-rasterization/dde8efb64f51b170e61a5d56b0dcb6774290e858/util/__init__.py -------------------------------------------------------------------------------- /util/getpx.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #define DLLEXPORT extern "C" __declspec(dllexport) 3 | DLLEXPORT void get_px(float area, int max_j, int px_num, 4 | float * c_arr, float * lattice_arr, float * px_arr) 5 | { 6 | float Ex[] = {0, 1, 1}; 7 | float Ey[] = {1, 0, 1}; 8 | float * p_lattice = lattice_arr; 9 | for (int i = 0; i < px_num; ++i) { 10 | float px = *p_lattice++; 11 | float py = *p_lattice++; 12 | float s = area; 13 | float * pc = c_arr; 14 | for (int j = 0; j <= max_j; ++j) { 15 | float exp_j = pow(2, j); 16 | float exp_jpx = exp_j * px; 17 | float exp_jpy = exp_j * py; 18 | for (int kx = 0; kx < exp_j; ++kx) { 19 | for (int ky = 0; ky < exp_j; ++ky) { 20 | float exp_jpkx = exp_jpx-kx; 21 | float exp_jpky = exp_jpy-ky; 22 | for (int i = 0; i < 3; ++i) { 23 | float c = *pc++; 24 | if (exp_jpkx < 0 || exp_jpkx >= 1) continue; 25 | if (exp_jpky < 0 || exp_jpky >= 1) continue; 26 | bool neg_x = (exp_jpkx >= 0.5 && Ex[i] != 0); 27 | bool neg_y = (exp_jpky >= 0.5 && Ey[i] != 0); 28 | if (neg_x && (!neg_y) || (!neg_x) && neg_y) 29 | s -= c; 30 | else 31 | s += c; 32 | } 33 | } 34 | } 35 | } 36 | px_arr[i] = s; 37 | } 38 | } -------------------------------------------------------------------------------- /util/getpx.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufoym/wavelet-rasterization/dde8efb64f51b170e61a5d56b0dcb6774290e858/util/getpx.dll -------------------------------------------------------------------------------- /util/getpx.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | import os 3 | os.environ['PATH'] = os.path.dirname(__file__) + ';' + os.environ['PATH'] 4 | api = CDLL('getpx.dll') 5 | 6 | def get_px(area, max_j, all_c, lattice): 7 | lattice_arr = [p[i] for p in lattice for i in xrange(2)] 8 | lattice_arr = (c_float * len(lattice_arr))(*lattice_arr) 9 | c_arr = [c for j in xrange(max_j+1) \ 10 | for kx in xrange(2**j) for ky in xrange(2**j) \ 11 | for c in all_c[(j, kx,ky)]] 12 | c_arr = (c_float * len(c_arr))(*c_arr) 13 | area = c_float(area) 14 | px_num, max_j = c_int(len(lattice)), c_int(max_j) 15 | px_arr = (c_float * len(lattice))(*([0] * len(lattice))) 16 | api.get_px(area, max_j, px_num, c_arr, lattice_arr, px_arr) 17 | return [px for px in px_arr] -------------------------------------------------------------------------------- /util/getpx_compile.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | g++ -c -o getpx.obj getpx.cpp 3 | g++ -shared -o getpx.dll getpx.obj 4 | del getpx.obj 5 | @pause -------------------------------------------------------------------------------- /util/solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #define EQN_EPS 1e-9 3 | #define DLLEXPORT extern "C" __declspec(dllexport) 4 | 5 | /******************************************************** 6 | * * 7 | * This function determines if a double is small enough * 8 | * to be zero. The purpose of the subroutine is to try * 9 | * to overcome precision problems in math routines. * 10 | * * 11 | ********************************************************/ 12 | 13 | static int isZero(double x) 14 | { 15 | return x > -EQN_EPS && x < EQN_EPS; 16 | } 17 | 18 | 19 | DLLEXPORT int solveLinear(double c1, double c0, 20 | double & s0) 21 | { 22 | if (isZero(c1)) 23 | return 0; 24 | s0 = - c0 / c1; 25 | return 1; 26 | } 27 | 28 | 29 | 30 | /******************************************************** 31 | * * 32 | * This function determines the roots of a quadric * 33 | * equation. * 34 | * It takes as parameters a pointer to the three * 35 | * coefficient of the quadric equation (the c[2] is the * 36 | * coefficient of x2 and so on) and a pointer to the * 37 | * two element array in which the roots are to be * 38 | * placed. * 39 | * It outputs the number of roots found. * 40 | * * 41 | ********************************************************/ 42 | 43 | DLLEXPORT int solveQuadric(double c2, double c1, double c0, 44 | double & s0, double & s1) 45 | { 46 | double p, q, D; 47 | 48 | // make sure we have a d2 equation 49 | 50 | if (isZero(c2)) 51 | return solveLinear(c1, c0, s0); 52 | 53 | 54 | // normal for: x^2 + px + q 55 | p = c1 / (2.0 * c2); 56 | q = c0 / c2; 57 | D = p * p - q; 58 | 59 | if (isZero(D)) 60 | { 61 | // one double root 62 | s0 = s1 = -p; 63 | return 1; 64 | } 65 | 66 | if (D < 0.0) 67 | // no real root 68 | return 0; 69 | 70 | else 71 | { 72 | // two real roots 73 | double sqrt_D = sqrt(D); 74 | s0 = sqrt_D - p; 75 | s1 = -sqrt_D - p; 76 | return 2; 77 | } 78 | } 79 | 80 | 81 | 82 | /******************************************************** 83 | * * 84 | * This function determines the roots of a cubic * 85 | * equation. * 86 | * It takes as parameters a pointer to the four * 87 | * coefficient of the cubic equation (the c[3] is the * 88 | * coefficient of x3 and so on) and a pointer to the * 89 | * three element array in which the roots are to be * 90 | * placed. * 91 | * It outputs the number of roots found * 92 | * * 93 | ********************************************************/ 94 | 95 | DLLEXPORT int solveCubic(double c3, double c2, double c1, double c0, 96 | double & s0, double & s1, double & s2) 97 | { 98 | int i, num; 99 | double sub, 100 | A, B, C, 101 | sq_A, p, q, 102 | cb_p, D; 103 | 104 | if (isZero(c3)) 105 | return solveQuadric(c2, c1, c0, s0, s1); 106 | 107 | // normalize the equation:x ^ 3 + Ax ^ 2 + Bx + C = 0 108 | A = c2 / c3; 109 | B = c1 / c3; 110 | C = c0 / c3; 111 | 112 | // substitute x = y - A / 3 to eliminate the quadric term: x^3 + px + q = 0 113 | 114 | sq_A = A * A; 115 | p = 1.0/3.0 * (-1.0/3.0 * sq_A + B); 116 | q = 1.0/2.0 * (2.0/27.0 * A *sq_A - 1.0/3.0 * A * B + C); 117 | 118 | // use Cardano's formula 119 | 120 | cb_p = p * p * p; 121 | D = q * q + cb_p; 122 | 123 | if (isZero(D)) 124 | { 125 | if (isZero(q)) 126 | { 127 | // one triple solution 128 | s0 = 0.0; 129 | num = 1; 130 | } 131 | else 132 | { 133 | // one single and one double solution 134 | double u = cbrt(-q); 135 | s0 = 2.0 * u; 136 | s1 = - u; 137 | num = 2; 138 | } 139 | } 140 | else 141 | if (D < 0.0) 142 | { 143 | // casus irreductibilis: three real solutions 144 | double phi = 1.0/3.0 * acos(-q / sqrt(-cb_p)); 145 | double t = 2.0 * sqrt(-p); 146 | s0 = t * cos(phi); 147 | s1 = -t * cos(phi + M_PI / 3.0); 148 | s2 = -t * cos(phi - M_PI / 3.0); 149 | num = 3; 150 | } 151 | else 152 | { 153 | // one real solution 154 | double sqrt_D = sqrt(D); 155 | double u = cbrt(sqrt_D + fabs(q)); 156 | if (q > 0.0) 157 | s0 = - u + p / u ; 158 | else 159 | s0 = u - p / u; 160 | num = 1; 161 | } 162 | 163 | // resubstitute 164 | sub = 1.0 / 3.0 * A; 165 | s0 -= sub; 166 | s1 -= sub; 167 | s2 -= sub; 168 | return num; 169 | } 170 | -------------------------------------------------------------------------------- /util/solver.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufoym/wavelet-rasterization/dde8efb64f51b170e61a5d56b0dcb6774290e858/util/solver.dll -------------------------------------------------------------------------------- /util/solver.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | import os 3 | os.environ['PATH'] = os.path.dirname(__file__) + ';' + os.environ['PATH'] 4 | api = CDLL('solver.dll') 5 | 6 | def linear(a, b): 7 | c1, c0 = c_double(a), c_double(b) 8 | x = [c_double()] 9 | num = api.solveLinear(c1, c0, byref(x[0])) 10 | return [x[i].value for i in xrange(num)] 11 | 12 | def quadric(a, b, c): 13 | c2, c1, c0 = c_double(a), c_double(b), c_double(c) 14 | x = [c_double(), c_double()] 15 | num = api.solveQuadric(c2, c1, c0, byref(x[0]), byref(x[1])) 16 | return [x[i].value for i in xrange(num)] 17 | 18 | def cubic(a, b, c, d): 19 | c3, c2, c1, c0 = c_double(a), c_double(b), c_double(c), c_double(d) 20 | x = [c_double(), c_double(), c_double()] 21 | num = api.solveCubic(c3, c2, c1, c0, byref(x[0]), byref(x[1]), byref(x[2])) 22 | return [x[i].value for i in xrange(num)] 23 | 24 | if __name__ == '__main__': 25 | from random import randint 26 | import time 27 | 28 | print cubic(2,-4,-22, 24) # [4, -3, 1] 29 | print cubic(3,-10,14, 27) # [-1] 30 | print cubic(1, 6, 12, 8) # [-2] 31 | 32 | eps = 1e-9 33 | ts = time.time() 34 | for i in xrange(1000000): 35 | a,b,c,d = randint(0,100),randint(0,100),randint(0,100),randint(0,100) 36 | for x in linear(a, b): 37 | if abs(a*x + b) > eps: print 'l', 38 | for x in quadric(a, b, c): 39 | if abs(a*x**2 + b*x + c) > eps: print 'q', 40 | for x in cubic(a, b, c, d): 41 | if abs(a*x**3 + b*x**2 + c*x + d) > eps: print 'c', 42 | print time.time() - ts -------------------------------------------------------------------------------- /util/solver_compile.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | g++ -c -o solver.obj solver.cpp 3 | g++ -shared -o solver.dll solver.obj 4 | del solver.obj --------------------------------------------------------------------------------