379 lines
9.3 KiB
C
379 lines
9.3 KiB
C
//========================================================================
|
|
//
|
|
// SplashMath.h
|
|
//
|
|
// Copyright 2003-2013 Glyph & Cog, LLC
|
|
//
|
|
//========================================================================
|
|
|
|
#ifndef SPLASHMATH_H
|
|
#define SPLASHMATH_H
|
|
|
|
#include <aconf.h>
|
|
|
|
#if USE_FIXEDPONT
|
|
# include "FixedPoint.h"
|
|
#else
|
|
# include <math.h>
|
|
# if (defined(__GNUC__) && defined(__SSE2__)) || \
|
|
(defined(_WIN32) && (_M_IX86_FP == 2 || defined(_M_X64)))
|
|
# include <emmintrin.h>
|
|
# endif
|
|
#endif
|
|
#include "SplashTypes.h"
|
|
|
|
static inline SplashCoord splashAbs(SplashCoord x) {
|
|
#if USE_FIXEDPOINT
|
|
return FixedPoint::abs(x);
|
|
#else
|
|
return fabs(x);
|
|
#endif
|
|
}
|
|
|
|
// floor() and (int)() are implemented separately, which results
|
|
// in changing the FPCW multiple times - so we optimize it with
|
|
// some inline assembly or SSE intrinsics.
|
|
static inline int splashFloor(SplashCoord x) {
|
|
#if USE_FIXEDPOINT
|
|
|
|
//--- fixed point
|
|
|
|
return FixedPoint::floor(x);
|
|
|
|
#elif (defined(__GNUC__) && defined(__SSE2__)) || \
|
|
(defined(_WIN32) && (_M_IX86_FP == 2 || defined(_M_X64)))
|
|
|
|
//--- SSE2 intrinsics
|
|
// NB: 64-bit x86 guarantees availability of SSE2.
|
|
|
|
__m128d m1, m2;
|
|
int i, s;
|
|
|
|
m1 = _mm_set_sd(x);
|
|
i = _mm_cvttsd_si32(m1);
|
|
m2 = _mm_cvtsi32_sd(m1, i);
|
|
s = _mm_ucomigt_sd(m2, m1);
|
|
return i - s;
|
|
|
|
#elif defined(__GNUC__) && defined(__i386__) && !defined(__APPLE__)
|
|
|
|
//--- x87 inline assembly (gcc/clang)
|
|
// (this code fails on OSX for reasons I don't understand)
|
|
|
|
Gushort oldCW, newCW, t;
|
|
int result;
|
|
|
|
__asm__ volatile("fnstcw %0\n"
|
|
"movw %0, %3\n"
|
|
"andw $0xf3ff, %3\n"
|
|
"orw $0x0400, %3\n"
|
|
"movw %3, %1\n" // round down
|
|
"fldcw %1\n"
|
|
"fistl %2\n"
|
|
"fldcw %0\n"
|
|
: "=m" (oldCW), "=m" (newCW), "=m" (result), "=r" (t)
|
|
: "t" (x));
|
|
return result;
|
|
|
|
#elif defined(_WIN32) && defined(_M_IX86)
|
|
|
|
//--- x87 inline assembly (VC)
|
|
|
|
Gushort oldCW, newCW;
|
|
int result;
|
|
|
|
__asm fld QWORD PTR x
|
|
__asm fnstcw WORD PTR oldCW
|
|
__asm mov ax, WORD PTR oldCW
|
|
__asm and ax, 0xf3ff
|
|
__asm or ax, 0x0400
|
|
__asm mov WORD PTR newCW, ax // round down
|
|
__asm fldcw WORD PTR newCW
|
|
__asm fistp DWORD PTR result
|
|
__asm fldcw WORD PTR oldCW
|
|
return result;
|
|
|
|
#else
|
|
|
|
//--- all others
|
|
|
|
return (int)floor(x);
|
|
|
|
#endif
|
|
}
|
|
|
|
// ceil() and (int)() are implemented separately, which results
|
|
// in changing the FPCW multiple times - so we optimize it with
|
|
// some inline assembly or SSE intrinsics.
|
|
static inline int splashCeil(SplashCoord x) {
|
|
#if USE_FIXEDPOINT
|
|
|
|
//--- fixed point
|
|
|
|
return FixedPoint::ceil(x);
|
|
|
|
#elif (defined(__GNUC__) && defined(__SSE2__)) || \
|
|
(defined(_WIN32) && (_M_IX86_FP == 2 || defined(_M_X64)))
|
|
|
|
//--- SSE2 intrinsics
|
|
// NB: 64-bit x86 guarantees availability of SSE2.
|
|
|
|
__m128d m1, m2;
|
|
int i, s;
|
|
|
|
m1 = _mm_set_sd(x);
|
|
i = _mm_cvttsd_si32(m1);
|
|
m2 = _mm_cvtsi32_sd(m1, i);
|
|
s = _mm_ucomilt_sd(m2, m1);
|
|
return i + s;
|
|
|
|
#elif defined(__GNUC__) && defined(__i386__) && !defined(__APPLE__)
|
|
|
|
//--- x87 inline assembly (gcc/clang)
|
|
// (this code fails on OSX for reasons I don't understand)
|
|
|
|
Gushort oldCW, newCW, t;
|
|
int result;
|
|
|
|
__asm__ volatile("fnstcw %0\n"
|
|
"movw %0, %3\n"
|
|
"andw $0xf3ff, %3\n"
|
|
"orw $0x0800, %3\n"
|
|
"movw %3, %1\n" // round up
|
|
"fldcw %1\n"
|
|
"fistl %2\n"
|
|
"fldcw %0\n"
|
|
: "=m" (oldCW), "=m" (newCW), "=m" (result), "=r" (t)
|
|
: "t" (x));
|
|
return result;
|
|
|
|
#elif defined(_WIN32) && defined(_M_IX86)
|
|
|
|
//--- x87 inline assembly (VC)
|
|
|
|
// ceil() and (int)() are implemented separately, which results
|
|
// in changing the FPCW multiple times - so we optimize it with
|
|
// some inline assembly
|
|
Gushort oldCW, newCW;
|
|
int result;
|
|
|
|
__asm fld QWORD PTR x
|
|
__asm fnstcw WORD PTR oldCW
|
|
__asm mov ax, WORD PTR oldCW
|
|
__asm and ax, 0xf3ff
|
|
__asm or ax, 0x0800
|
|
__asm mov WORD PTR newCW, ax // round up
|
|
__asm fldcw WORD PTR newCW
|
|
__asm fistp DWORD PTR result
|
|
__asm fldcw WORD PTR oldCW
|
|
return result;
|
|
|
|
#else
|
|
|
|
//--- all others
|
|
|
|
return (int)ceil(x);
|
|
|
|
#endif
|
|
}
|
|
|
|
static inline int splashRound(SplashCoord x) {
|
|
#if USE_FIXEDPOINT
|
|
|
|
//--- fixed point
|
|
|
|
return FixedPoint::round(x);
|
|
|
|
#else
|
|
|
|
//--- all others
|
|
|
|
return splashFloor(x + 0.5);
|
|
|
|
#endif
|
|
}
|
|
|
|
static inline SplashCoord splashAvg(SplashCoord x, SplashCoord y) {
|
|
#if USE_FIXEDPOINT
|
|
return FixedPoint::avg(x, y);
|
|
#else
|
|
return 0.5 * (x + y);
|
|
#endif
|
|
}
|
|
|
|
static inline SplashCoord splashSqrt(SplashCoord x) {
|
|
#if USE_FIXEDPOINT
|
|
return FixedPoint::sqrt(x);
|
|
#else
|
|
return sqrt(x);
|
|
#endif
|
|
}
|
|
|
|
static inline SplashCoord splashPow(SplashCoord x, SplashCoord y) {
|
|
#if USE_FIXEDPOINT
|
|
return FixedPoint::pow(x, y);
|
|
#else
|
|
return pow(x, y);
|
|
#endif
|
|
}
|
|
|
|
static inline SplashCoord splashDist(SplashCoord x0, SplashCoord y0,
|
|
SplashCoord x1, SplashCoord y1) {
|
|
SplashCoord dx, dy;
|
|
dx = x1 - x0;
|
|
dy = y1 - y0;
|
|
#if USE_FIXEDPOINT
|
|
// this handles the situation where dx*dx or dy*dy is too large to
|
|
// fit in the 16.16 fixed point format
|
|
SplashCoord dxa, dya, d;
|
|
dxa = splashAbs(dx);
|
|
dya = splashAbs(dy);
|
|
if (dxa == 0 && dya == 0) {
|
|
return 0;
|
|
} else if (dxa > dya) {
|
|
d = dya / dxa;
|
|
return dxa * FixedPoint::sqrt(d*d + 1);
|
|
} else {
|
|
d = dxa / dya;
|
|
return dya * FixedPoint::sqrt(d*d + 1);
|
|
}
|
|
#else
|
|
return sqrt(dx * dx + dy * dy);
|
|
#endif
|
|
}
|
|
|
|
static inline GBool splashCheckDet(SplashCoord m11, SplashCoord m12,
|
|
SplashCoord m21, SplashCoord m22,
|
|
SplashCoord epsilon) {
|
|
#if USE_FIXEDPOINT
|
|
return FixedPoint::checkDet(m11, m12, m21, m22, epsilon);
|
|
#else
|
|
return fabs(m11 * m22 - m12 * m21) >= epsilon;
|
|
#endif
|
|
}
|
|
|
|
// Perform stroke adjustment on a SplashCoord range [xMin, xMax),
|
|
// resulting in an int range [*xMinI, *xMaxI).
|
|
//
|
|
// There are several options:
|
|
//
|
|
// 1. Round both edge coordinates.
|
|
// Pro: adjacent strokes/fills line up without any gaps or
|
|
// overlaps
|
|
// Con: lines with the same original floating point width can
|
|
// end up with different integer widths, e.g.:
|
|
// xMin = 10.1 xMax = 11.3 (width = 1.2)
|
|
// --> xMinI = 10 xMaxI = 11 (width = 1)
|
|
// but
|
|
// xMin = 10.4 xMax = 11.6 (width = 1.2)
|
|
// --> xMinI = 10 xMaxI = 12 (width = 2)
|
|
//
|
|
// 2. Round the min coordinate; add the ceiling of the width.
|
|
// Pro: lines with the same original floating point width will
|
|
// always end up with the same integer width
|
|
// Con: adjacent strokes/fills can have overlaps (which is
|
|
// problematic with transparency)
|
|
// (This could use floor on the min coordinate, instead of
|
|
// rounding, with similar results.)
|
|
// (If the width is rounded instead of using ceiling, the results
|
|
// Are similar, except that adjacent strokes/fills can have gaps
|
|
// as well as overlaps.)
|
|
//
|
|
// 3. Use floor on the min coordinate and ceiling on the max
|
|
// coordinate.
|
|
// Pro: lines always end up at least as wide as the original
|
|
// floating point width
|
|
// Con: adjacent strokes/fills can have overlaps, and lines with
|
|
// the same original floating point width can end up with
|
|
// different integer widths; the integer width can be more
|
|
// than one pixel wider than the original width, e.g.:
|
|
// xMin = 10.9 xMax = 12.1 (width = 1.2)
|
|
// --> xMinI = 10 xMaxI = 13 (width = 3)
|
|
// but
|
|
// xMin = 10.1 xMax = 11.3 (width = 1.2)
|
|
// --> xMinI = 10 xMaxI = 12 (width = 2)
|
|
//
|
|
// 4. Use a hybrid approach, choosing between two of the above
|
|
// options, based on width. E.g., use #2 if width <= 4, and use #1
|
|
// if width > 4.
|
|
//
|
|
// If w >= 0 and strokeAdjMode is splashStrokeAdjustCAD then a special
|
|
// mode for projecting line caps is enabled, with w being the
|
|
// transformed line width.
|
|
|
|
static inline void splashStrokeAdjust(SplashCoord xMin, SplashCoord xMax,
|
|
int *xMinI, int *xMaxI,
|
|
SplashStrokeAdjustMode strokeAdjMode,
|
|
SplashCoord w = -1) {
|
|
int x0, x1;
|
|
|
|
// make sure the coords fit in 32-bit ints
|
|
#if USE_FIXEDPOINT
|
|
if (xMin < -32767) {
|
|
xMin = -32767;
|
|
} else if (xMin > 32767) {
|
|
xMin = 32767;
|
|
}
|
|
if (xMax < -32767) {
|
|
xMax = -32767;
|
|
} else if (xMax > 32767) {
|
|
xMax = 32767;
|
|
}
|
|
#else
|
|
if (xMin < -1e9) {
|
|
xMin = -1e9;
|
|
} else if (xMin > 1e9) {
|
|
xMin = 1e9;
|
|
}
|
|
if (xMax < -1e9) {
|
|
xMax = -1e9;
|
|
} else if (xMax > 1e9) {
|
|
xMax = 1e9;
|
|
}
|
|
#endif
|
|
|
|
// this will never be called with strokeAdjMode == splashStrokeAdjustOff
|
|
if (strokeAdjMode == splashStrokeAdjustCAD) {
|
|
x0 = splashRound(xMin);
|
|
if (w >= 0) {
|
|
x1 = splashRound(xMax - w) + splashRound(w);
|
|
} else {
|
|
x1 = x0 + splashRound(xMax - xMin);
|
|
}
|
|
} else {
|
|
// NB: enable exactly one of these.
|
|
#if 1 // 1. Round both edge coordinates.
|
|
x0 = splashRound(xMin);
|
|
x1 = splashRound(xMax);
|
|
#endif
|
|
#if 0 // 2. Round the min coordinate; add the ceiling of the width.
|
|
x0 = splashRound(xMin);
|
|
x1 = x0 + splashCeil(xMax - xMin);
|
|
#endif
|
|
#if 0 // 3. Use floor on the min coord and ceiling on the max coord.
|
|
x0 = splashFloor(xMin);
|
|
x1 = splashCeil(xMax);
|
|
#endif
|
|
#if 0 // 4. Hybrid.
|
|
SplashCoord w = xMax - xMin;
|
|
x0 = splashRound(xMin);
|
|
if (w > 4) {
|
|
x1 = splashRound(xMax);
|
|
} else {
|
|
x1 = x0 + splashRound(w);
|
|
}
|
|
#endif
|
|
}
|
|
if (x0 == x1) {
|
|
if (xMin + xMax < 2 * x0) {
|
|
--x0;
|
|
} else {
|
|
++x1;
|
|
}
|
|
}
|
|
*xMinI = x0;
|
|
*xMaxI = x1;
|
|
}
|
|
|
|
#endif
|