Skip to content
This repository has been archived by the owner on Jan 17, 2020. It is now read-only.

Commit

Permalink
added join type option to shape.to_outline
Browse files Browse the repository at this point in the history
  • Loading branch information
Youka committed Nov 5, 2014
1 parent b1af7b0 commit 6bdf5e0
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 21 deletions.
4 changes: 2 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ <h1>Yutils<span>An ASS typeset utilities library</span></h1>
Splits lines of shape <span class="function_parameters">shape</span> into shorter ones to fix to maximal line length of <span class="function_parameters">max_len</span>, creating a new shape.
</div>
<div class="function">
outline_shape = shape.to_outline(shape, width_xy[, width_y])<br>
Converts shape <span class="function_parameters">shape</span> from his filling to the stroke with horizontal width <span class="function_parameters">width_xy</span> and vertical width <span class="function_parameters">width_y</span>, returned as new shape. If <span class="function_parameters">width_y</span> isn't defined, <span class="function_parameters">width_xy</span> stands for both.
outline_shape = shape.to_outline(shape, width_xy[, width_y][, mode])<br>
Converts shape <span class="function_parameters">shape</span> from his filling to the stroke with horizontal width <span class="function_parameters">width_xy</span>, vertical width <span class="function_parameters">width_y</span> and join type <span class="function_parameters">mode</span> which can be "round"|"bevel"|"miter", returned as new shape. If <span class="function_parameters">width_y</span> isn't defined, <span class="function_parameters">width_xy</span> stands for both. Default join type is "round".
</div>
<div class="function">
pixels = shape.to_pixels(shape)<br>
Expand Down
81 changes: 63 additions & 18 deletions src/Yutils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------------------------------------------
Version: 26th October 2014, 19:10 (GMT+1)
Version: 4th November 2014, 04:00 (GMT+1)
Yutils
table
Expand Down Expand Up @@ -60,7 +60,7 @@
glue(src_shape, dst_shape[, transform_callback]) -> string
move(shape, x, y) -> string
split(shape, max_len) -> string
to_outline(shape, width_xy[, width_y]) -> string
to_outline(shape, width_xy[, width_y][, mode]) -> string
to_pixels(shape) -> table
transform(shape, matrix) -> string
ass
Expand Down Expand Up @@ -113,6 +113,7 @@
local FP_PRECISION = 3 -- Floating point precision by numbers behind point (for shape points)
local CURVE_TOLERANCE = 1 -- Angle in degree to define a curve as flat
local MAX_CIRCUMFERENCE = 1.5 -- Circumference step size to create round edges out of lines
local MITER_LIMIT = 200 -- Maximal length of a miter join
local SUPERSAMPLING = 8 -- Anti-aliasing precision for shape to pixels conversion
local FONT_PRECISION = 64 -- Font scale for better precision output from native font system
local LIBASS_FONTHACK = true -- Scale font data to fontsize? (no effect on windows)
Expand Down Expand Up @@ -1591,12 +1592,14 @@ Yutils = {
return table.concat(new_shape, " ")
end,
-- Converts shape to stroke version
to_outline = function(shape, width_xy, width_y)
to_outline = function(shape, width_xy, width_y, mode)
-- Check arguments
if type(shape) ~= "string" or type(width_xy) ~= "number" or width_xy < 0 or not (width_y == nil or type(width_y) == "number" and width_y >= 0) then
error("shape and line width (general or horizontal and vertical) expected", 2)
elseif width_y and not (width_xy > 0 or width_y > 0) or width_xy == 0 then
if type(shape) ~= "string" or type(width_xy) ~= "number" or width_y ~= nil and type(width_y) ~= "number" or mode ~= nil and type(mode) ~= "string" then
error("shape, line width (general or horizontal and vertical) and optional mode expected", 2)
elseif width_y and (width_xy < 0 or width_y < 0 or not (width_xy > 0 or width_y > 0)) or width_xy <= 0 then
error("one width must be >0", 2)
elseif mode and mode ~= "round" and mode ~= "bevel" and mode ~= "miter" then
error("valid mode expected", 2)
end
-- Line width values
local width, xscale, yscale
Expand Down Expand Up @@ -1694,25 +1697,67 @@ Yutils = {
o_vec1_x, o_vec1_y = Yutils.math.stretch(o_vec1_x, o_vec1_y, 0, width)
local o_vec2_x, o_vec2_y = Yutils.math.ortho(post_point[1]-point[1], post_point[2]-point[2], 0, 0, 0, 1)
o_vec2_x, o_vec2_y = Yutils.math.stretch(o_vec2_x, o_vec2_y, 0, width)
-- Calculate degree & circumference between orthogonal vectors
local degree = Yutils.math.degree(o_vec1_x, o_vec1_y, 0, o_vec2_x, o_vec2_y, 0)
local circ = math.abs(math.rad(degree)) * width
-- Add first edge point
outline_n = outline_n + 1
outline[outline_n] = string.format("%s%s %s",
outline_n == 1 and "m " or outline_n == 2 and "l " or "",
outline_n == 1 and "m " or "",
Yutils.math.round(point[1] + o_vec1_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + o_vec1_y * yscale, FP_PRECISION))
-- Round edge needed?
if circ > MAX_CIRCUMFERENCE then
local circ_rest = circ % MAX_CIRCUMFERENCE
for cur_circ = circ_rest > 0 and circ_rest or MAX_CIRCUMFERENCE, circ, MAX_CIRCUMFERENCE do
local curve_vec_x, curve_vec_y = rotate2d(o_vec1_x, o_vec1_y, cur_circ / circ * degree)
-- Create join by mode
if mode == "bevel" then
-- Nothing to add!
elseif mode == "miter" then
-- Add mid edge point(s)
local vec1_x, vec1_y, vec2_x, vec2_y = point[1]-pre_point[1], point[2]-pre_point[2], point[1]-post_point[1], point[2]-post_point[2]
local delta = vec1_x * vec2_y - vec2_x * vec1_y
if delta == 0 then -- Parallel vectors
vec1_x, vec1_y = Yutils.math.stretch(vec1_x, vec1_y, 0, MITER_LIMIT)
vec2_x, vec2_y = Yutils.math.stretch(vec2_x, vec2_y, 0, MITER_LIMIT)
outline_n = outline_n + 1
outline[outline_n] = string.format("%s%s %s",
outline_n == 1 and "m " or outline_n == 2 and "l " or "",
Yutils.math.round(point[1] + curve_vec_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + curve_vec_y * yscale, FP_PRECISION))
outline[outline_n] = string.format("%s%s %s %s %s",
outline_n == 2 and "l " or "",
Yutils.math.round(point[1] + (o_vec1_x + vec1_x) * xscale, FP_PRECISION), Yutils.math.round(point[2] + (o_vec1_y + vec1_y) * yscale, FP_PRECISION),
Yutils.math.round(point[1] + (o_vec2_x + vec2_x) * xscale, FP_PRECISION), Yutils.math.round(point[2] + (o_vec2_y + vec2_y) * yscale, FP_PRECISION))
else -- Vectors intersect
local pre, post = (point[1] + o_vec1_x) * (point[2] + o_vec1_y + vec1_y) - (point[1] + o_vec1_x + vec1_x) * (point[2] + o_vec1_y),
(point[1] + o_vec2_x) * (point[2] + o_vec2_y + vec2_y) - (point[1] + o_vec2_x + vec2_x) * (point[2] + o_vec2_y)
local is_vec_x, is_vec_y = (pre * -vec2_x - post * -vec1_x) / delta - point[1], (pre * -vec2_y - post * -vec1_y) / delta - point[2]
local is_vec_len = Yutils.math.distance(is_vec_x, is_vec_y)
if is_vec_len > MITER_LIMIT then
local fix_scale = MITER_LIMIT / is_vec_len
outline_n = outline_n + 1
outline[outline_n] = string.format("%s%s %s %s %s",
outline_n == 2 and "l " or "",
Yutils.math.round(point[1] + (o_vec1_x + (is_vec_x - o_vec1_x) * fix_scale) * xscale, FP_PRECISION), Yutils.math.round(point[2] + (o_vec1_y + (is_vec_y - o_vec1_y) * fix_scale) * yscale, FP_PRECISION),
Yutils.math.round(point[1] + (o_vec2_x + (is_vec_x - o_vec2_x) * fix_scale) * xscale, FP_PRECISION), Yutils.math.round(point[2] + (o_vec2_y + (is_vec_y - o_vec2_y) * fix_scale) * yscale, FP_PRECISION))
else
outline_n = outline_n + 1
outline[outline_n] = string.format("%s%s %s",
outline_n == 2 and "l " or "",
Yutils.math.round(point[1] + is_vec_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + is_vec_y * yscale, FP_PRECISION))
end
end
else -- not mode or mode == "round"
-- Calculate degree & circumference between orthogonal vectors
local degree = Yutils.math.degree(o_vec1_x, o_vec1_y, 0, o_vec2_x, o_vec2_y, 0)
local circ = math.abs(math.rad(degree)) * width
-- Join needed?
if circ > MAX_CIRCUMFERENCE then
-- Add curve edge points
local circ_rest = circ % MAX_CIRCUMFERENCE
for cur_circ = circ_rest > 0 and circ_rest or MAX_CIRCUMFERENCE, circ - MAX_CIRCUMFERENCE, MAX_CIRCUMFERENCE do
local curve_vec_x, curve_vec_y = rotate2d(o_vec1_x, o_vec1_y, cur_circ / circ * degree)
outline_n = outline_n + 1
outline[outline_n] = string.format("%s%s %s",
outline_n == 2 and "l " or "",
Yutils.math.round(point[1] + curve_vec_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + curve_vec_y * yscale, FP_PRECISION))
end
end
end
-- Add end edge point
outline_n = outline_n + 1
outline[outline_n] = string.format("%s%s %s",
outline_n == 2 and "l " or "",
Yutils.math.round(point[1] + o_vec2_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + o_vec2_y * yscale, FP_PRECISION))
end
-- Insert inner or outer outline to stroke shape
stroke_shape_n = stroke_shape_n + 1
Expand Down
2 changes: 1 addition & 1 deletion tests/shape.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ print("Flattened shape: " .. Yutils.shape.flatten(shape))
print("Moved shape: " .. Yutils.shape.move(shape, 5, -2))
print("Mirrored shape: " .. Yutils.shape.transform(shape, Yutils.math.create_matrix().rotate("y", 180)))
print("Shape with splitted lines: " .. Yutils.shape.split(shape, 15))
print("Shape outline: " .. Yutils.shape.to_outline(Yutils.shape.flatten(shape), 5.5, 10))
print("Shape outline: " .. Yutils.shape.to_outline(Yutils.shape.flatten(shape), 5.5, 10, "miter"))
print("Pixels:\n" .. Yutils.table.tostring(Yutils.shape.to_pixels("m -4.5 -4 l 0 -4 0 0")))
print(
"Detected shapes:\n" ..
Expand Down

0 comments on commit 6bdf5e0

Please sign in to comment.