Skip to content

Commit

Permalink
refactor: Move PRNG outside framebox and improve usage
Browse files Browse the repository at this point in the history
  • Loading branch information
Omikhleia authored and Didier Willis committed Jan 13, 2024
1 parent 7df50ac commit 1ab7e1e
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 39 deletions.
9 changes: 8 additions & 1 deletion packages/framebox/graphics/renderer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
--

local RoughGenerator = require("rough-lua.rough.generator").RoughGenerator
local PRNG = require("prng-prigarin")

-- HELPERS

Expand Down Expand Up @@ -305,9 +306,15 @@ function DefaultPainter.draw (_, drawable, clippable)
end

local RoughPainter = pl.class()
local prng = PRNG()

function RoughPainter:_init (options)
self.gen = RoughGenerator(options)
local o = options or {}
if not o.randomizer then
o.randomizer = prng -- use common 'static' PRNG instance
-- so that all sketchy drawings look random but reproducible
end
self.gen = RoughGenerator(o)
end

function RoughPainter:line (x1, y1, x2, y2, options)
Expand Down
2 changes: 1 addition & 1 deletion packages/framebox/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ Sketching options are
\autodoc:parameter{roughness} (numerical value indicating how rough the drawing is; 0 would be a perfect rectangle, the default value is 1 and there is no upper limit to this value but a value over 10 is mostly useless),
\autodoc:parameter{bowing} (numerical value indicating how curvy the lines are when
drawing a sketch; a value of 0 will cause straight lines and the default value is 1),
\autodoc:parameter{preserve} (defaults to false; when set to true, the locations of the end points are not randomized),
\autodoc:parameter{preserve} (defaults to false; when set to true, the \roughbox[bordercolor=#22427c, preserve=true]{locations} of the end points are not randomized),
\autodoc:parameter{singlestroke} (defaults to false; if set to true, a single stroke is applied
to sketch the shape instead of multiple strokes).
For instance, here is a single-stroked \roughbox[bordercolor=#59b24c, singlestroke=true]{rough box,}
Expand Down
26 changes: 14 additions & 12 deletions packages/framebox/graphics/prng.lua → prng-prigarin/init.lua
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
--
-- Pseudo-Random Number Generator (PRNG)
-- License: MIT
-- 2022, 2023 Didier Willis
--
-- Why would a text processing software such as SILE need a PRNG,
-- where one would expect the reproduceability of the output?
--
-- Well, there are algorithms were a bit of randomness is expected
-- e.g. the rough "hand-drawn-like" drawing style, where one would
-- expect all rough graphics to look different.
-- But using math.random() there would yield always different results...
-- There are algorithms were a bit of randomness is expected, but
-- where one would expect a reproducible output.
-- Using math.random() there would yield always different results...
-- and using math.randomseed() is also problematic: it's global and could be
-- affected elsewhere, etc.
-- So one may need instead a "fake" PRNG, that spits out a seemingly uniform
-- distribution of "random" numbers.

-- ([email protected]) The algorithm below was just found on the
-- Internet, where it was stated to be common in Monte Carlo randomizations.
--
-- The algorithm below was just found on the Internet, where it was stated to
-- be "common in Monte Carlo randomizations."
--
-- I am not so lazy not to check, and traced it back to Sergei M. Prigarin,
-- _Spectral Models of Random Fields in Monte Carlo Methods_, 2001.
Expand All @@ -25,8 +22,8 @@
-- This derivation, if I read correctly, has a 2^40 module and 5^17 mutiplier
-- (cycle length 2^38).
-- For information; the seeds are (X1, X2), here set to (0, 1). The algorithm
-- could be seeded with other values. It's not clear to me which variant was
-- used (I didn't check the whole book...), but it seems the constraints are
-- can be seeded with other values.
-- I didn't check the whole book...), but it seems the constraints are
-- 0 < X1, X2 <= 2^20 and X2 being odd.

local A1, A2 = 727595, 798405 -- 5^17=D20*A1+A2
Expand All @@ -35,6 +32,11 @@ local D20, D40 = 1048576, 1099511627776 -- 2^20, 2^40
local PRNG = pl.class({
X1 = 0,
X2 = 1,
_init = function (self, seed)
if seed then -- Just seeding X1
self.X1 = math.abs(seed) % D20
end
end,
random = function (self)
local U = self.X2 * A2
local V = (self.X1 * A2 + self.X2 * A1) % D20
Expand Down
8 changes: 5 additions & 3 deletions rough-lua/rough/fillers/scan-line-hachure.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ local hachureLines = require("rough-lua.rough.fillers.hachure-fill").hachureLine
local jsshims = require("rough-lua.rough.jsshims")
local math_round = jsshims.math_round

local PRNG = require("packages.framebox.graphics.prng")
local prng = PRNG()
local PRNG = require("prng-prigarin")

local function polygonHachureLines (polygonList, o)
local angle = o.hachureAngle + 90
Expand All @@ -24,7 +23,10 @@ local function polygonHachureLines (polygonList, o)
gap = math_round(math.max(gap, 0.1))
local skipOffset = 1
if o.roughness >= 1 then
if prng:random() > 0.7 then
-- PORTING NOTE: Slightly different approach to randomization.
-- We never rely on math.random() but always use our PRNG.
local rand = o.randomizer and o.randomizer:random() or PRNG(o.seed or 0):random()
if rand > 0.7 then
skipOffset = gap
end
end
Expand Down
18 changes: 6 additions & 12 deletions rough-lua/rough/generator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,12 @@ local line, rectangle,
renderer.patternFillArc, renderer.patternFillPolygons, renderer.solidFillPolygon
-- PORTING NOTE:
-- I ported the module but haven't tested it for now
-- local curveToBezier = require("packages.framebox.points-on-curve.curve-to-bezier").curveToBezier
-- local pointsOnPath = require("packages.framebox.points-on-curve").pointsOnPath
-- local pointsOnBezierCurves = require("packages.framebox.points-on-curve").pointsOnBezierCurves
local pointsOnPath = function (_, _, _)
error("Not implemented")
end
local curveToBezier = function (_)
error("Not implemented")
end
local pointsOnBezierCurves = function (_, _, _)
error("Not implemented")
end
-- local curveToBezier = require("rough-lua.points-on-curve.curve-to-bezier").curveToBezier
-- local pointsOnPath = require("rough-lua.points-on-curve").pointsOnPath
-- local pointsOnBezierCurves = require("rough-lua.points-on-curve").pointsOnBezierCurves
local pointsOnPath = function () error("Not implemented") end
local curveToBezier = function () error("Not implemented") end
local pointsOnBezierCurves = function () error("Not implemented") end


local RoughGenerator = pl.class({
Expand Down
28 changes: 18 additions & 10 deletions rough-lua/rough/renderer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,36 @@

local jsshims = require("rough-lua.rough.jsshims")
local array_concat = jsshims.array_concat

local PRNG = require("packages.framebox.graphics.prng")
local prng = PRNG()
local PRNG = require("prng-prigarin")

-- PORTING NOTE:
-- I ported path-data-parser but haven't tested it for now
-- local pathDataParser = require("packages.framebox.path-data-parser")
-- local pathDataParser = require("rough-lua.path-data-parser")
-- local parsePath, normalize, absolutize = pathDataParser.parsePath, pathDataParser.normalize, pathDataParser.absolutize
local normalize = function () error("Not yet implemented") end
local absolutize = function () error("Not yet implemented") end
local parsePath = function () error("Not yet implemented") end

local getFiller = require("rough-lua.rough.fillers.filler").getFiller

local function cloneOptionsAlterSeed (o)
-- PORTING NOTE:
-- Option to alter seed no implemented.
return o
local function cloneOptionsAlterSeed (ops)
local result = pl.tablex.copy(ops)
result.randomizer = nil
if ops.seed then
result.seed = ops.seed + 1
end
return result
end

local function random (ops)
if not ops.randomizer then
ops.randomizer = PRNG(ops.seed or 0)
end
return ops.randomizer:random()
end

local function _offset (min, max, ops, roughnessGain)
return ops.roughness * (roughnessGain or 1) * ((prng:random() * (max - min)) + min)
return ops.roughness * (roughnessGain or 1) * ((random(ops) * (max - min)) + min)
end

local function _offsetOpt (x, ops, roughnessGain)
Expand All @@ -55,7 +63,7 @@ local function _line (x1, y1, x2, y2, o, move, overlay)
offset = length / 10
end
local halfOffset = offset / 2
local divergePoint = 0.2 + prng:random() * 0.2
local divergePoint = 0.2 + random(o) * 0.2
local midDispX = o.bowing * o.maxRandomnessOffset * (y2 - y1) / 200
local midDispY = o.bowing * o.maxRandomnessOffset * (x1 - x2) / 200
midDispX = _offsetOpt(midDispX, o, roughnessGain)
Expand Down

0 comments on commit 1ab7e1e

Please sign in to comment.