[#47] LCB snippet: Transforming a list of points.
One of the statements for creating a path is to use
set <path> to (polygon | polyline) path with points <list of points>
After that you can transform the created path but this doesn't include the corresponding transform of the source. You have to get it from the instructions of the path or to do it yourself. The last option is easily done with the following handlers.
This following an application of
peter-b's snippet (#42) and will us enable to act on a list of non-empty points, similar to available statements for paths
- affine transform the points by setting a transform matrix
- scale the points by giving scale-factors
- skew the points by giving skew-factors and center locations
- rotate the points by giving positive (=cw) or negative angles (=ccw)
- translate the points by giving translate-shifts
and, not available for paths,
- perspective transform the points by setting a distortion list
This is the core.
Code: Select all
--mark snippet #42 @peter-b
handler type ApplyHandler(in pValue as optional any) returns optional any
handler applyToList(in pHandler as ApplyHandler, in pValues as List) returns List
variable tResult as List
variable tValue as optional any
repeat for each element tValue in pValues
push pHandler(tValue) onto tResult
end repeat
return tResult
end handler
handler transformPointsBy(in pNumList as List, in pAction as String) returns List
if pAction is "affine" then
return applyToList(affinePoints,pNumList)
else if pAction is "rotate" then
return applyToList(rotatePoints,pNumList)
else if pAction is "scale" then
return applyToList(scalePoints,pNumList)
else if pAction is "skew" then
return applyToList(skewPoints,pNumList)
else if pAction is "translate" then
return applyToList(translatePoints,pNumList)
else if pAction is "perspective" then
return applyToList(perspectivePoints,pNumList)
else
return applyToList(identityPoints,pNumList)
end if
end handler
Then we use (the variables to be defined as in the full 'widget-test' example below) the following handlers.
Code: Select all
handler scalePoints(in pEach as optional any) returns optional any
return point [mXFactor*the x of pEach, mYFactor*the y of pEach]
end handler
-- skewX is in x-direction (first coord)
-- skewY is in y-direction (second coord)
handler skewPoints(in pEach as optional any) returns optional any
return point [the x of pEach+mXSkew*(the y of pEach-mYCenter), \
the y of pEach-mYSkew*(the x of pEach-mXCenter)]
end handler
handler rotatePoints(in pEach as optional any) returns optional any
return point \
[mXCenter + mCos*(the x of pEach-mXCenter)-mSin*(the y of pEach-mYCenter), \
mYCenter + mCos*(the y of pEach-mYCenter)+mSin*(the x of pEach-mXCenter)]
end handler
handler translatePoints(in pEach as optional any) returns optional any
return point [mXShift + the x of pEach, mYShift + the y of pEach]
end handler
handler affinePoints(in pEach as optional any) returns optional any
return point [mAffine[1]*(the x of pEach)+mAffine[2]*(the y of pEach)+mAffine[5], \
mAffine[3]*(the x of pEach)+mAffine[4]*(the y of pEach)+mAffine[6]]
end handler
-- first 6 elements of mPerspective are affine
-- last two elements are scaling the "distortion"
-- if tM[7] and tm[8] are zero we have an affine transform!
handler perspectivePoints(in pEach as optional any) returns optional any
variable tX
variable tY
variable tD
variable tM
set tM to mPerspective
set tX to the x of pEach
set tY to the y of pEach
set tD to (tM[7]*tX + tM[8]*tY + 100)/100 --> changed <--
return point [(tM[1]*tX + tM[2]*tY + tM[5])/tD, (tM[3]*tX + tM[4]*tY + tM[6])/tD]
end handler
handler identityPoints(in pEach as optional any) returns optional any
return pEach
end handler
Once again is here a
full 'widget-test' example, using snippets #42,#43,#46 (and #47).
[Just copy, save as the only .lcb file in some folder, load it (by the topRight folder icon) in the Extension builder and hit "Test".]
This includes also utilites
multiplyTransform and
inverseAffine for 'affine matrices' (lists of 6 elements).
Code: Select all
widget community.livecode.hermann.test02
use com.livecode.math
use com.livecode.canvas
use com.livecode.widget
use com.livecode.engine
metadata title is "test02"
metadata author is "hh"
metadata version is "0.0.1"
metadata preferredSize is "320,275"
private variable mE as ScriptObject
private variable mDown as Boolean
private variable mClickPosition as Point
private variable mList as List
private variable mXFactor as Number
private variable mYFactor as Number
private variable mXShift as Number
private variable mYShift as Number
private variable mXSkew as Number
private variable mYSkew as Number
private variable mCos as Number
private variable mSin as Number
private variable mXCenter as Number
private variable mYCenter as Number
private variable mXDistort as Number
private variable mYDistort as Number
private variable mAffine as List
private variable mPerspective as List
private variable mTest as List
private variable mDo as String
property "perspectiveList" get getPerspective set setPerspective
metadata perspectiveList.editor is "com.livecode.pi.string"
metadata perspectiveList.default is "1,0,0,1,0,0,0,0"
--mark snippet #42 @peter-b
handler type ApplyHandler(in pValue as optional any) returns optional any
handler applyToList(in pHandler as ApplyHandler, in pValues as List) returns List
variable tResult as List
variable tValue as optional any
repeat for each element tValue in pValues
push pHandler(tValue) onto tResult
end repeat
return tResult
end handler
handler transformPointsBy(in pNumList as List, in pAction as String) returns List
if pAction is "affine" then
return applyToList(affinePoints,pNumList)
else if pAction is "rotate" then
return applyToList(rotatePoints,pNumList)
else if pAction is "scale" then
return applyToList(scalePoints,pNumList)
else if pAction is "skew" then
return applyToList(skewPoints,pNumList)
else if pAction is "translate" then
return applyToList(translatePoints,pNumList)
else if pAction is "perspective" then
return applyToList(perspectivePoints,pNumList)
else if pAction is "ztest" then
return applyToList(ztestPoints,pNumList)
else
return applyToList(identityPoints,pNumList)
end if
end handler
handler translatePoints(in pEach as optional any) returns optional any
return point [mXShift + the x of pEach, mYShift + the y of pEach]
end handler
-- skewX is in x-direction (first coord)
-- skewY is in y-direction (second coord)
handler skewPoints(in pEach as optional any) returns optional any
return point [the x of pEach+mXSkew*(the y of pEach-mYCenter), \
the y of pEach-mYSkew*(the x of pEach-mXCenter)]
end handler
handler scalePoints(in pEach as optional any) returns optional any
return point [mXFactor*the x of pEach, mYFactor*the y of pEach]
end handler
handler rotatePoints(in pEach as optional any) returns optional any
return point \
[mXCenter + mCos*(the x of pEach-mXCenter)-mSin*(the y of pEach-mYCenter), \
mYCenter + mCos*(the y of pEach-mYCenter)+mSin*(the x of pEach-mXCenter)]
end handler
handler affinePoints(in pEach as optional any) returns optional any
return point [mAffine[1]*(the x of pEach)+mAffine[2]*(the y of pEach)+mAffine[5], \
mAffine[3]*(the x of pEach)+mAffine[4]*(the y of pEach)+mAffine[6]]
end handler
-- first 6 elements of mPerspective are affine
-- last two elements are scaling the "distortion"
handler perspectivePoints(in pEach as optional any) returns optional any
variable tX
variable tY
variable tD
variable tM
set tM to mPerspective -- sx, ry, rx, sy, tx, ty, px, py
set tX to the x of pEach
set tY to the y of pEach
set tD to (tM[7]*tX + tM[8]*tY + 100)/100 --> changed <--
return point [(tM[1]*tX + tM[2]*tY + tM[5])/tD, (tM[3]*tX + tM[4]*tY + tM[6])/tD]
end handler
handler identityPoints(in pEach as optional any) returns optional any
return pEach
end handler
-- use for testRuns
handler ztestPoints(in pEach as optional any) returns optional any
--return (pEach formatted as string) & "_"
--put hhNumberToString(4*pEach,2,2) into pEach
--replace "." with ":" in pEach
variable tX
variable tY
set tX to the x of pEach
set tY to the y of pEach
return point [tX,tX^mTest[1]*tY^mTest[2]]
end handler
public handler OnCreate()
put my script object into mE
set property "width" of mE to 300
set property "height" of mE to 245
set property "top" of mE to 10
set property "left" of mE to 10
put rectPoints([40,70,160,170]) into mList
put "perspective" into mDo
set mPerspective to [2,0,0,1,0,0,0.3,0.01]
-- inverseAffine Test:
-- log multiplyTransform([1,-0.5,0.25,1,50,20],inverseAffine([1,-0.5,0.25,1,50,20]))
post "set width of this stack to 320; set height of this stack to 265;" & \
"choose browse tool"
end handler
public handler OnMouseDown()
if the click button is 3 then
put false into mDown
popup menu "affine\nrotate\nscale\nskew\ntranslate\nperspective\nztest" at the click position
if the result is not nothing then
put the result into mDo
end if
else
put true into mDown
put the click position into mClickPosition
end if
redraw all
end handler
public handler OnMouseUp()
put false into mDown
redraw all
end handler
public handler OnMouseRelease()
put false into mDown
redraw all
end handler
public handler OnPaint()
variable tList as List
variable tString as String
variable tPath as Path
variable tP as List
variable tPath0 as Path
set tPath0 to polyline path with points mList
set the paint of this canvas to solid paint with color [1.0,0.0,0.0]
stroke tPath0 on this canvas
-- set private variables here to see better what they will do
if mDo is "affine" then
set tP to [1,-0.5,0.25,1,80,-20]
set mAffine to tP
else if mDo is "rotate" then
set tP to [-60,100,120]
set mCos to cos(tP[1]*pi/180)
set mSin to sin(tP[1]*pi/180)
set mXCenter to tP[2]
set mYCenter to tP[3]
else if mDo is "scale" then
set tP to [0.4,1.2]
set mXFactor to tP[1]
set mYFactor to tP[2]
else if mDo is "skew" then
set tP to [25,0,100,120]
set mXSkew to sin(-tP[1]*pi/180)
set mYSkew to sin(-tP[2]*pi/180)
set mXCenter to tP[3]
set mYCenter to tP[4]
else if mDo is "translate" then
set tP to [100,10]
set mXShift to tP[1]
set mYShift to tP[2]
else if mDo is "perspective" then
--set tP to [2,0,0,1,0,0,0.3,0.01]
--set mPerspective to tP
set tP to mPerspective
set mXDistort to tP[7]
set mYDistort to tP[8]
else if mDo is "ztest" then
set tP to [0.3,0.7]
set mTest to tP
end if
set tPath to polyline path with points transformPointsBy(mList,mDo)
-- set the font of this canvas to font "Monaco" at size 12
set the size of the font of this canvas to 13
set the paint of this canvas to solid paint with color [0.2,0.2,0.2]
fill text "RightClick to switch transform." at point [10,10] on this canvas
set tList to numListToStringList(tP)
combine tList with "," into tString
replace "," with ", " in tString
fill text "<\q"&mDo&"\q, " & tString & ">" at point [10,28] on this canvas
set the dashes of this canvas to [1,2,6,2]
set the paint of this canvas to solid paint with color [0.8,1.0,1.0]
fill tPath on this canvas
set the paint of this canvas to solid paint with color [0.0,0.0,1.0]
set the join style of this canvas to "miter"
set the stroke width of this canvas to 1
stroke tPath on this canvas
if mDown then
variable tRect as Rectangle
if hhPointIsInTransformedRect(mClickPosition,the instructions of tPath) then
set tString to "INSIDE"
set the paint of this canvas to solid paint with color [1.0,1.0,0.5]
fill tPath on this canvas
set the dashes of this canvas to []
set the paint of this canvas to solid paint with color [1.0,0.5,0.0]
stroke tPath on this canvas
else
set tString to "OUTSIDE"
end if
--mark ClickLabel
set tRect to the image bounds of text tString on this canvas
set the width of tRect to 4 + the width of tRect
set the height of tRect to 4 + the height of tRect
set the left of tRect to -2 + the x of mClickPosition + the left of tRect
set the top of tRect to -2 + the y of mClickPosition + the top of tRect
set the paint of this canvas to solid paint with color [0.8,1.0,1.0]
fill rectangle path of tRect on this canvas
set the paint of this canvas to solid paint with color [0,0,0]
fill text tString at mClickPosition on this canvas
end if
end handler
handler getPerspective() returns String
variable tList as any
variable tString as any
put numListToStringList(mPerspective) into tList
combine tList with "," into tString
return tString
end handler
handler setPerspective(in pList as String) returns nothing
variable tList as List
split pList by "," into tList
set mDo to "perspective"
set mPerspective to tList parsed as list of number
redraw all
end handler
-- utility handler
handler multiplyTransform(in pL as List, in pR as List)
return [pL[1]*pR[1]+pL[2]*pR[3], pL[1]*pR[2]+pL[2]*pR[4], \
pL[1]*pR[5]+pL[2]*pR[6]+pL[5], \
pL[3]*pR[1]+pL[4]*pR[3], pL[3]*pR[2]+pL[4]*pR[4],\
pL[3]*pR[5]+pL[4]*pR[6]+pL[6], 0, 0, 1]
end handler
-- pM is a list of 6 Numbers (affine transformation matrix)
handler inverseAffine(in pM as List) returns List
variable tD as Number
set tD to pM[1]*pM[4] - pM[2]*pM[3] -- determinant
if tD = 0 then
return [0,0,0,0,0,0] --> into doc
else
return [pM[4]/tD, -pM[2]/tD, - pM[3]/tD, pM[1]/tD, \
(pM[2]*pM[6] - pM[5]*pM[4])/tD, (pM[3]*pM[5] - pM[1]*pM[6])/tD]
end if
end handler
handler numListToStringList(in pNumList as List) returns List
return applyToList(formatToString,pNumList)
end handler
handler formatToString(in pEach as optional any) returns optional any
return pEach formatted as string
end handler
handler rectPoints(in pN as List) returns List
return [point [pN[1],pN[2]],point [pN[3],pN[2]], \
point [pN[3],pN[4]],point [pN[1],pN[4]], point [pN[1],pN[2]]]
end handler
-- end utility handler
--mark snippet #43:
-- pLeadNum the at least leading digits of the integer part
-- pDecNum the exact number of decimals, last one rounded, filled up with zeros
private handler hhNumberToString(in pNum as Number,in pLeadNum as Number, in pDecNum as Number) returns String
variable tS as String
variable tN as Number
variable tC as Number
put the empty string into tS
if pNum < 0 then
put "-" into tS
multiply pNum by -1
else
put "" into tS
end if
put the rounded of ((the rounded of 10^(pDecNum+1)*pNum)/10) into tN
if tN = 0 then
repeat pLeadNum+pDecNum times
put "0" after tS
end repeat
else
repeat with tC from \
the maximum of pLeadNum+pDecNum-1 and (the trunc of the log of tN) down to 0
put the trunc of (tN/10^tC) formatted as string after tS
put tN mod 10^tC into tN
end repeat
end if
if pDecNum > 0 then -- fractional part
put "." before char -pDecNum of tS
end if
return tS
end handler
--mark snippet #46:
-- pString is the instructions of a transformed rectangle path
private handler hhPointIsInTransformedRect(in pPoint as Point, in pString as String) returns Boolean
return pointInShape(pPoint, hhInstructionsToPoints(pString))
end handler
-- pString is the instructions of a transformed rectangle path
private handler hhInstructionsToPoints(in pString as String) returns List
variable tS as String
variable tL as List
variable tL1 as List
variable tOff as Number
variable tN as Number
replace "-" with " -" in pString
replace "L -" with "L-" in pString
replace "M -" with "M-" in pString
put the first offset of "L" in pString into tOff
put char 2 to (tOff-1) of pString into tS
put " " & char (tOff+1) to -2 of pString after tS
split tS by " " into tL
put the empty list into tL1
put the number of elements in tL into tN
if tN > 1 then
push point [element 1 of tL parsed as number, \
element 2 of tL parsed as number] onto tL1
end if
if tN > 3 then
push point [element 3 of tL parsed as number, \
element 4 of tL parsed as number] onto tL1
end if
if tN > 5 then
push point [element 5 of tL parsed as number, \
element 6 of tL parsed as number] onto tL1
end if
if tN > 7 then
push point [element 7 of tL parsed as number, \
element 8 of tL parsed as number] onto tL1
end if
if tN > 1 then
push point [element 1 of tL parsed as number, \
element 2 of tL parsed as number] onto tL1
end if
return tL1
end handler
-- ACKNOWLEDGEMENT.
-- This is essentially the subdivison algorithm by MShimrat (Aug 1962).
handler pointInShape(in pPoint as Point, in pList as List) returns Boolean
variable tX0 as Number
variable tY0 as Number
put the x of pPoint into tX0
put the y of pPoint into tY0
variable tCheck as Boolean
put false into tCheck
variable tC as Number
variable tX1 as Number
variable tX2 as Number
variable tY1 as Number
variable tY2 as Number
variable nJ as Number
put the number of elements in pList into nJ
repeat with tC from 1 up to nJ
put the x of pList[tC] into tX1
put the y of pList[tC] into tY1
put the x of pList[nJ] into tX2
put the y of pList[nJ] into tY2
if ( ((tY1 > tY0) is not (tY2 > tY0)) and \
( tX0 < tX1 + (tX2-tX1)*(tY0-tY1)/(tY2-tY1) ) ) then
put not tCheck into tCheck
end if
put tC into nJ
end repeat
return tCheck
end handler
end widget
In the property editor you can edit the perspective transform:
property 'perspectiveList' is given by a list of 8 elements. The first 6 elements are as usual defining the matrix of an affine transform, the last two are x- and y-distortion factors. I f these are zero, then we have an ordinary affine transform.
From script you can do (for example)
Code: Select all
get property "perspectiveList" of widget <widget name>
set property "perspectiveList" of widget to <list of 8 elements>
Example to test 'isolated' the distortion parameters:
Code: Select all
-- (The first six "1,0,0,1,0,0" are the identity affine transform)
set property "perspectiveList" of widget to "1,0,0,1,0,0,0.3,0.01"