Added Oneric code

This commit is contained in:
DataHoarder 2022-04-24 03:09:27 +02:00
parent 4dc3353573
commit b52b163aee
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
5 changed files with 632 additions and 0 deletions

229
ass_transform Normal file
View file

@ -0,0 +1,229 @@
!! Everything here is untested and best check for errors in the calculations !!
Choose \fay, \fax, \fscx and \fscy to create a (almost) general 2D-transform-matrix
(Rotations are sometimes also used)
(ASS Transform order: combined faxy, scale, frz, frx, fry)
If shearOrigin == scaleOrigin == rotationOrigin == (0,0)^T
(which iirc is true for drawings with \org(0,0)?),
and there are no run splits, and if assuming \frz0 for now,
ASS will effectively use the following transform matrix
| scale_x 0 | | 1 fax |
| | * | |
| 0 scale_y | | fay 1 |
| scale_x scale_x fax |
= | |
| scale_y fay scale_y |
Let's assume that's true.
Then given an arbitrary transform matrix M,
with known coefficients a, b, c, d \in \R
| a b |
M = | |
| c d |
we get:
I: scale_x = a
II: scale_y = d
III: scale_x fax = b
IV: scale_y fay = c
If (a = b = 0) or (c = d = 0), then scale_x = 0
or scale_y = 0 and all other values can be set to zero
as the event vanishes anyway.
If (a = 0 and b != 0) or (c != 0 and d = 0), then
we can't solve this as is directly. Ignore this
case for now, we'll later describe how to adapt the solution for this.
If scale_x = a != 0 != d = scale_y, it follows that
III => fax = b / scale_x
IV => fay = c / scale_y
But even in this case, there's one issue:
scale_x, scale_y may be negative,
but ASS only allows positive scale values.
To remedy this we'll use 3D rotations to emulate negative scale.
It is easy to see, that by themselves a
rotation around x by π, is equivalent to scale_y = -1 and
a rotation around y by π is equivalent to scale_x = -1.
If we use \fscx and \fscy to scale by the respective absolute values
and immediately follow this up by a “-1”-scale if needed the result
will be equivalent to a scale by the proper value.
In general this isn't so straightforward as ASS always applies rotations
in the order of z, x, y, but we chose rot_z = 0, resulting in it being a
dismissable identity operation.
If scale_x is negative, pass the absolute value to \fscx and add \fry180.
If scale_y is negative, pass the absolute value to \fscy and add \frx180.
ASS-3D-rotation matrices:
| 1 0 0 |
| |
Rx = | 0 cos(rot_x) sin(rot_x) |
| |
| 0 sin(rot_x) -cos(rot_x) |
| cos(rot_y) 0 sin(rot_y) |
| |
Ry = | 0 1 0 |
| |
| sin(rot_y) 0 -cos(rot_y) |
| cos(rot_z) -sin(rot_z) 0 |
| |
Rz = | sin(rot_z) cos(rot_z) 0 | (for choice of rot_z=0 identity matrix)
| |
| 0 0 1 |
(from: https://sourceforge.net/p/guliverkli2/code/HEAD/tree/src/subtitles/RTS.cpp#l148)
Using sgn_x, sgn_y \in {-1, 1} as the sign of the respective scales
and sc_x, sc_y \in { x >= 0 | x \in \R } as the absolute scale values,
all in all we get the following tags
\fry(-360*(sgn_x-1)) \frx(-360/(sgn_y-1)) \fscx(sc_x) \fscy(sc_y) \fax(fax) \fay(fay) \org(0,0)
which results in the ASS transform matrix:
| sgn_x 0 0 | | 1 0 0 | | sc_x sc_x fax 0 |
| | | | | |
| 0 1 0 | * | 0 sgn_y 0 | * | sc_y fay sc_y 0 |
| | | | | |
| 0 0 -sgn_x | | 0 0 -sgn_y | | 0 0 1 |
| sgn_x 0 0 | | sc_x sc_x fax 0 |
| | | |
= | 0 sgn_y 0 | * | sc_y fay sc_y 0 |
| | | |
| 0 0 sgn_x*sgn_y | | 0 0 1 |
| sgn_x*sc_x sgn_x*sc_x fax 0 |
| |
= | sgn_y*sc_y fay sgn_y*sc_y 0 |
| |
| 0 0 sgn_x*sgn_y |
using sgn_x*sc_x = scale_x and sgn_y*sc_y = scale_y
| scale_x scale_x fax 0 |
| |
= | scale_y fay scale_y 0 |
| |
| 0 0 sgn_x*sgn_y |
| a b 0 |
| |
= | c d 0 |
| |
| 0 0 sgn_x*sgn_y |
Apart from flipping the sign of the z-coordinates when sgn_x != sgn_y
this is exactly the matrix we wanted.
Since 2D-glyphs and -drawings are initially placed at z=0,
this additional sign-flip is of no concern.
---
Now coming back to the case of (a = 0 and b != 0) or (c != 0 and d = 0):
To be able to solve this we also need to apply a z-Rotation:
1) If in each column there's at least one non-zero entry or one row is all zero
(together with case condition the latter means only b xor c is non-zero):
Use \frz90, being a row swap with a sign flip:
| 0 -1 |
\frz90 = | |
| 1 0 |
So our transform-matrix equation becomes:
| -scale_y fay -scale_y | | a b |
| | = | |
| scale_x scale_x fax | | c d |
Which can be solved as before for signed scales.
The additon of a z-Rotation means our "scale sign"-roations
no longer immediately follow the actual scale.
To compensate, use \frx for sgn_x and \fry for sgn_y
(roles reversed from before)
2) Otherwise:
(If one column is zero and the other one has only non-zero entries)
2.1) a = 0 = c and b ≠ 0 ≠ d
| scale_x*cos(rz) - scale_y*fay*sin(rz) scale_x*fax*cos(rz) - scale_y*sin(rz) | | 0 b |
| | = | |
| scale_x*sin(rz) + scale_y*fay*cos(rz) scale_x*fax*sin(rz) + scale_y*cos(rz) | | 0 d |
I: scale_x*cos(rz) - scale_y*fay*sin(rz) = 0
II: scale_x*sin(rz) + scale_y*fay*cos(rz) = 0
III: scale_x*fax*cos(rz) - scale_y*sin(rz) = b
IV: scale_x*fax*sin(rz) + scale_y*cos(rz) = d
I² + II² ⇒ scale_x^2 = - scale_y^2 * fay^2 ⇒ scale_x = 0 and (scale_y = 0 or fay = 0)
III' : -scale_y*sin(rz) = b
IV' : scale_y*cos(rz) = d
⇒ since b ≠ 0 ≠ d ⇒ scale_y ≠ 0 ⇒ fay = 0
⇒ ||scale_y|| = sqrt(b^2 + d^2)
⇒ rz = tan⁻¹(-b/d)
Note: The conventional tan⁻¹ definition always returns positive cosine.
Thus we can always coose the positive solution for scale_y
and instead add π to rot_z if d < 0
Since scale_y > 0 and scale_x = 0 we do not need \frx and \fry to simulate a negative scale.
2.2) b = 0 = d and a ≠ 0 ≠ c
| scale_x*cos(rz) - scale_y*fay*sin(rz) scale_x*fax*cos(rz) - scale_y*sin(rz) | | a 0 |
| | = | |
| scale_x*sin(rz) + scale_y*fay*cos(rz) scale_x*fax*sin(rz) + scale_y*cos(rz) | | c 0 |
I: scale_x*fax*cos(rz) - scale_y*sin(rz) = 0
II: scale_x*fax*sin(rz) + scale_y*cos(rz) = 0
III: scale_x*cos(rz) - scale_y*fay*sin(rz) = a
IV: scale_x*sin(rz) + scale_y*fay*cos(rz) = b
I² + II² ⇒ scale_y^2 = - scale_x^2 * fax^2 ⇒ scale_y = 0 and (scale_x = 0 or fax = 0)
III' : scale_x*cos(rz) = a
IV' : scale_x*sin(rz) = c
⇒ since a ≠ 0 ≠ c ⇒ scale_x ≠ 0 ⇒ fax = 0
⇒ ||scale_x|| = sqrt(a^2 + c^2)
⇒ rz = tan⁻¹(c/a)
Note: The conventional tan⁻¹ definition always returns positive cosine.
Thus we can always coose the positive solution for scale_x
and instead add π to rot_z if a < 0
Since scale_x > 0 and scale_y = 0 we do not need \frx and \fry to simulate a negative scale.
In both 2.1) and 2.2) we get one dimension scaled to zero, and if I didin't miss something
no contradictions in the equations. Meaning the event actually vanishes and is visually identical
to choosing all parameters zero. If interpolations are involved having the mathematically accurate
transform matrix might still be good though.

13
cimpl/Makefile Normal file
View file

@ -0,0 +1,13 @@
#!/usr/bin/gmake
EXES := unstable stable
all: $(EXES)
$(EXES): %: %.c common.h
gcc -O2 -ftree-vectorize -fno-trapping-math -gdwarf \
-Wall -Wextra -Wpedantic \
-o $@ $^ -lm
clean:
rm -f $(EXES)

226
cimpl/common.h Normal file
View file

@ -0,0 +1,226 @@
#include <assert.h>
#include <float.h>
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// Just a quick poc, assumes size_t >= int
/* Matrices are represented as row-by-row, ltr, flattened matrix
* |a b|
* |c d| to {a, b, c, d}
*/
#define C_PI 3.14159265358979323846 /* matches glibc and musl */
typedef struct {
double scale_x; // >= 0
double scale_y; // >= 0
double fax;
double fay;
double rot_z;
bool rot_x; // false: 0; true 180
bool rot_y; // false: 0; true 180
} Tags;
void print_matrix(const char* name, const double* mat)
{
int nlen = strlen(name);
#define FLP "%8.4f"
int llen = printf("%*s | "FLP" "FLP" |", nlen, "", mat[0], mat[1]);
assert(llen > nlen + 5);
printf("\n%s = |%.*s|\n"
"%*s | "FLP" "FLP" |\n",
name, llen-nlen-5,
" ",
nlen, "", mat[2], mat[3]);
#undef FLP
}
void mult_matrices(const double* l, const double* r, double* res)
{
#define I(row,col) (col + (row << 1))
res[0] = l[I(0,0)] * r[I(0,0)] + l[I(0,1)] * r[I(1,0)];
res[1] = l[I(0,0)] * r[I(0,1)] + l[I(0,1)] * r[I(1,1)];
res[2] = l[I(1,0)] * r[I(0,0)] + l[I(1,1)] * r[I(1,0)];
res[3] = l[I(1,0)] * r[I(0,1)] + l[I(1,1)] * r[I(1,1)];
#undef I
}
void identity(double* mat)
{
mat[0] = 1.0; mat[1] = 0.0;
mat[2] = 0.0; mat[3] = 1.0;
}
#define COPY(src,dst) memcpy(dst, src, sizeof(double)*4)
bool matrix_equal(const double* a, const double *b, const double eps)
{
return fabs(a[0] - b[0]) <= eps
&& fabs(a[1] - b[1]) <= eps
&& fabs(a[2] - b[2]) <= eps
&& fabs(a[3] - b[3]) <= eps;
}
/**
* 0: same with default tolerance
* 1: same with 3 * default tolerance
* 2: same with 5 * default tolerance
* -1: not the same
*/
int matrix_compfuzzy(const double* a, const double* b)
{
for(int i = 0; i < 3; ++i) {
if(matrix_equal(a, b, 2000 * (2*i + 1) * DBL_EPSILON))
return i;
}
return -1;
}
/**
* Ignores z dimension
*/
void tags_to_matrix(const Tags* t, double* mat)
{
// ASS applies operations in order:
// combined-faxy "shear", scale, rot_z, rot_x, rot_y
double* cur = mat;
double nextop[4] = {0};
double tmp[4] = {0};
// Shear and Scale
cur[0] = t->scale_x;
cur[1] = t->scale_x * t->fax;
cur[2] = t->scale_y * t->fay;
cur[3] = t->scale_y;
/*print_matrix("scaleshear", cur);
puts("");*/
// Rotation: Z
double c = cos(t->rot_z * C_PI / 180.0);
double s = sin(t->rot_z * C_PI / 180.0);
nextop[0] = c; nextop[1] = -s;
nextop[2] = s; nextop[3] = c;
mult_matrices(nextop, cur, tmp);
COPY(tmp, cur);
/*print_matrix(" rotz", nextop);
print_matrix("rotz-scfa", cur);
puts("");*/
// Rotation: X
if (t->rot_x) {
identity(nextop);
nextop[3] = -1;
mult_matrices(nextop, cur, tmp);
COPY(tmp, cur);
/*print_matrix(" rotx", nextop);
print_matrix("rotx-zscfa", cur);
puts("");*/
}
// Rotation: Y
if (t->rot_y) {
identity(nextop);
nextop[0] = -1;
mult_matrices(nextop, cur, tmp);
COPY(tmp, cur);
/*print_matrix(" roty", nextop);
print_matrix("roty-xzscfa", cur);
puts("");*/
}
}
/**
* Call with len=0 to get required size for untruncated output
*/
int tags_to_string(const Tags* t, char* str, size_t len)
{
size_t p = 0;
if(t->rot_y) {
size_t tmp = snprintf(str, len, "\\fry180");
p += tmp;
if(len <= tmp) {
len = 0;
str = NULL;
} else {
len -= tmp;
str += tmp;
}
}
if(t->rot_x) {
size_t tmp = snprintf(str, len, "\\frx180");
p += tmp;
if(len <= tmp) {
len = 0;
str = NULL;
} else {
len -= tmp;
str += tmp;
}
}
if(t->rot_z) {
size_t tmp = snprintf(str, len, "\\frz%.5f ", t->rot_z);
p += tmp;
if(len <= tmp) {
len = 0;
str = NULL;
} else {
len -= tmp;
str += tmp;
}
}
p += snprintf(str, len, "\\fscy%.5f\\fscx%.5f \\fay%.5f\\fax%.5f",
t->scale_y * 100.0, t->scale_x * 100.0, t->fay, t->fax);
return p;
}
//const double DEF_TARGET[4] = {-0.06203, 0.19083, -0.0758, 0.00354};
const double DEF_TARGET[4] = {-0.12395, 0.42227, -0.17882, 0.04262};
bool parse_args(int argc, char** argv, double* target)
{
if(argc <= 1)
memcpy(target, DEF_TARGET, sizeof(DEF_TARGET));
else if(argc == 5) {
target[0] = strtod(argv[1], NULL);
target[1] = strtod(argv[2], NULL);
target[2] = strtod(argv[3], NULL);
target[3] = strtod(argv[4], NULL);
} else {
fprintf(stderr, "Usage: %s [<a> <b> <c> <d>]\n", argv[0]);
return false;
}
return true;
}
int evaluate_result(const double* tar, const Tags* tags)
{
double result[4] = {0};
char tag_str[4096] = {0};
tags_to_matrix(tags, result);
tags_to_string(tags, tag_str, 4096);
printf("\n %s\n", tag_str);
print_matrix("result", result);
puts("");
int score = matrix_compfuzzy(tar, result);
const char* score_name;
switch(score) {
case 0: score_name = "same(high)"; break;
case 1: score_name = "same(med)"; break;
case 2: score_name = "same"; break;
default: score_name = "FAIL"; break;
}
printf("Score: %s\n", score_name);
return score < 0;
}

39
cimpl/test.sh Executable file
View file

@ -0,0 +1,39 @@
#!/bin/sh
if [ "$#" -ne 1 ] ; then
echo "Usage $0 <executable>" >&2
exit 2
fi
NAME="$(basename $1)"
cnt_fail=0
cnt_succ=0
printf "" > failure_"$NAME".log
printf "" > success_"$NAME".log
for sa in "" - ; do
for sb in "" - ; do
for sc in "" - ; do
for sd in "" - ; do
for a in 5 0 ; do
for b in 11 0 ; do
for c in 3 0 ; do
for d in 19 0 ; do
outp="$(./"$1" $sa$a $sb$b $sc$c $sd$d 2>/dev/null)"
if [ "$?" -ne 0 ] ; then
cnt_fail="$((cnt_fail+1))"
printf '\n\n' >> failure_"$NAME".log
printf "%s\n" "$outp" >> failure_"$NAME".log
else
cnt_succ="$((cnt_succ+1))"
printf '\n\n' >> success_"$SNAME".log
printf "%s\n" "$outp" >> success_"$NAME".log
fi
done ; done ; done ; done
done ; done ; done ; done
printf " %3d SUCCESSES\n" "$cnt_succ"
printf " %3d FAILURES\n" "$cnt_fail"
printf " %3d total\n" "$((cnt_succ + cnt_fail))"

125
cimpl/unstable.c Normal file
View file

@ -0,0 +1,125 @@
#include "common.h"
inline bool zero(double d)
{
return fabs(d) < 700 * DBL_EPSILON;
// For reference musl (64bit double) uses:
// DBL_EPSILON 2.22044604925031308085e-16
}
/*
* a == 0 \implies b == 0
* and
* d == 0 \implies c == 0
*/
void case_trivial(const double* m, Tags* t)
{
t->scale_x = m[0];
t->scale_y = m[3];
t->fax = zero(m[0]) ? 0 : (m[1] / m[0]); // case condition
t->fay = zero(m[3]) ? 0 : (m[2] / m[3]); // case condition
if(t->scale_x < 0) {
t->rot_y = true;
t->scale_x = fabs(t->scale_x);
}
if(t->scale_y < 0) {
t->rot_x = true;
t->scale_y = fabs(t->scale_y);
}
}
/**
* |0 b| |0 0| |0 b| |0 b| |a b|
* |0 0|, |c 0|, |c 0|, |c d| or |c 0| with letters non-zero
* (i.e. c==0 \implies d==0 and a==0 \implies d==0)
*
* apply rot_z of 90° to get
* |a b| |-scale_y*fay -scale_y |
* |c d| = | scale_x scale_x*fax|
*/
void case_rowswap(const double* m, Tags* t)
{
t->rot_z = 90;
t->scale_x = m[2];
t->scale_y = -m[1];
t->fax = zero(m[2]) ? 0 : (m[3] / m[2]); // case condition
t->fay = zero(m[1]) ? 0 : (m[0] / m[1]); // case condition
if(t->scale_x < 0) {
t->rot_x = true;
t->scale_x = fabs(t->scale_x);
}
if(t->scale_y < 0) {
t->rot_y = true;
t->scale_y = fabs(t->scale_y);
}
}
/**
* |a 0|
* |c 0| with letters non-zero
*/
void case_zerocol_r(const double* m, Tags* t)
{
t->scale_y = 0;
t->fax = 0;
t->fay = 0; // Arbitrary
t->scale_x = sqrt(m[0] * m[0] + m[2] * m[2]);
t->rot_z = atan(m[2] / m[0]) * 180 / C_PI;
if (m[0] < 0) // atan always yields positive cos
t->rot_z += 180;
}
/**
* |0 b|
* |0 d| with letters non-zero
*/
void case_zerocol_l(const double* m, Tags* t)
{
t->scale_x = 0;
t->fay = 0;
t->fax = 0; // Arbitrary
t->scale_y = sqrt(m[1] * m[1] + m[3] * m[3]);
t->rot_z = atan(-m[1] / m[3]) * 180 / C_PI;
if (m[3] < 0) // atan always yields positive cos
t->rot_z += 180;
}
int main(int argc, char** argv)
{
double tar[4] = {0};
if(!parse_args(argc, argv, tar))
exit(5);
print_matrix("target", tar);
Tags tags = {0};
if(
!( (zero(tar[0]) && !zero(tar[1]))
|| (zero(tar[3]) && !zero(tar[2])) )
) {
printf(" --trivial\n");
case_trivial(tar, &tags);
} else if(
!( (zero(tar[1]) && !zero(tar[0]))
|| (zero(tar[2]) && !zero(tar[3])) )
) {
printf(" --rowswap\n");
case_rowswap(tar, &tags);
} else if(
zero(tar[0]) && zero(tar[2]) && !zero(tar[1]) && !zero(tar[3])
) {
printf(" --zerocol_l\n");
case_zerocol_l(tar, &tags);
} else if(
!zero(tar[0]) && !zero(tar[2]) && zero(tar[1]) && zero(tar[3])
) {
printf(" --zerocol_r\n");
case_zerocol_r(tar, &tags);
} else {
fprintf(stderr, "Oupsie, unknown matrix-case encountered. This shouldn't happen :P\n");
exit(6);
}
return evaluate_result(tar, &tags);
}