8e70ffac38
ADDED: * envs.MustEnv(), envs.MustEnvNoBlank() UPDATED: * dep mods, Go version
653 lines
13 KiB
Go
653 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
`math`
|
|
`slices`
|
|
"strconv"
|
|
`strings`
|
|
|
|
mapset `github.com/deckarep/golang-set/v2`
|
|
)
|
|
|
|
type (
|
|
AspectRatio struct {
|
|
ar float64
|
|
arStr string
|
|
arW float64
|
|
arH float64
|
|
gcd int
|
|
res *Resolution
|
|
}
|
|
Resolution struct {
|
|
w int
|
|
h int
|
|
ar *AspectRatio
|
|
}
|
|
)
|
|
|
|
func (a *AspectRatio) AsFloat() (ar float64) {
|
|
|
|
if a == nil {
|
|
return
|
|
}
|
|
|
|
if a.ar == 0 {
|
|
a.ar = float64(a.res.w) / float64(a.res.h)
|
|
}
|
|
|
|
ar = a.ar
|
|
|
|
return
|
|
}
|
|
|
|
func (a *AspectRatio) AsSlice() (ar [2]float64) {
|
|
|
|
if a.arW == 0 {
|
|
a.arW = float64(a.res.w) / float64(a.GCD())
|
|
}
|
|
if a.arH == 0 {
|
|
a.arH = float64(a.res.h) / float64(a.GCD())
|
|
}
|
|
|
|
ar = [2]float64{
|
|
a.arW,
|
|
a.arH,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
Cmp is a comparison function that can be used for sorting.
|
|
|
|
The return cmp value has the same meaning as in [slices.SortFunc]:
|
|
|
|
If a comes before other, cmp == -1
|
|
If a comes after other, cmp == 1
|
|
If a and other are equal, cmp == 0
|
|
*/
|
|
func (a *AspectRatio) Cmp(other AspectRatio) (cmp int) {
|
|
|
|
var aSlice [2]float64
|
|
var oSlice [2]float64
|
|
|
|
if a == nil {
|
|
return
|
|
}
|
|
|
|
aSlice = a.AsSlice()
|
|
oSlice = other.AsSlice()
|
|
|
|
if aSlice[0] == oSlice[0] && aSlice[1] == oSlice[1] {
|
|
return
|
|
}
|
|
if aSlice[0] == oSlice[0] {
|
|
if aSlice[1] < oSlice[1] {
|
|
cmp = -1
|
|
} else {
|
|
cmp = 1
|
|
}
|
|
} else {
|
|
if aSlice[0] < oSlice[0] {
|
|
cmp = -1
|
|
} else {
|
|
cmp = 1
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (a *AspectRatio) Equals(other AspectRatio) (isEqual bool) {
|
|
|
|
isEqual = a.Cmp(other) == 0
|
|
|
|
return
|
|
}
|
|
|
|
func (a *AspectRatio) FloatString() (s string) {
|
|
|
|
if a == nil {
|
|
return
|
|
}
|
|
|
|
if a.ar == 0 {
|
|
a.ar = float64(a.res.w) / float64(a.res.h)
|
|
}
|
|
|
|
s = strconv.FormatFloat(a.ar, 'f', -1, 64)
|
|
|
|
return
|
|
}
|
|
|
|
func (a *AspectRatio) GCD() (gcd int) {
|
|
|
|
var x int
|
|
var y int
|
|
|
|
if a == nil {
|
|
return
|
|
}
|
|
|
|
if a.gcd == 0 {
|
|
x = a.res.w
|
|
y = a.res.h
|
|
// Euclidean GCD ("Greatest Common Divisor") algorithm
|
|
for y != 0 {
|
|
x, y = y, x%y
|
|
}
|
|
a.gcd = x
|
|
}
|
|
|
|
gcd = a.gcd
|
|
|
|
return
|
|
}
|
|
|
|
func (a *AspectRatio) Resolution() (r Resolution) {
|
|
|
|
var ar [2]float64
|
|
var resInts [2]int
|
|
|
|
if a == nil {
|
|
return
|
|
}
|
|
|
|
resInts = a.res.AsSlice()
|
|
ar = a.AsSlice()
|
|
|
|
// Return a fully populated copy.
|
|
r = Resolution{
|
|
w: resInts[0],
|
|
h: resInts[1],
|
|
}
|
|
r.ar = &AspectRatio{
|
|
ar: a.AsFloat(),
|
|
arStr: a.String(),
|
|
arW: ar[0],
|
|
arH: ar[1],
|
|
gcd: a.GCD(),
|
|
res: &r,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (a *AspectRatio) String() (s string) {
|
|
|
|
if a == nil {
|
|
return
|
|
}
|
|
|
|
if a.arStr == "" {
|
|
if a.arW == 0 {
|
|
a.arW = float64(a.res.w) / float64(a.GCD())
|
|
}
|
|
if a.arH == 0 {
|
|
a.arH = float64(a.res.h) / float64(a.GCD())
|
|
}
|
|
a.arStr = fmt.Sprintf(
|
|
"%d:%d",
|
|
int(math.Round(a.arW)),
|
|
int(math.Round(a.arH)),
|
|
)
|
|
}
|
|
|
|
s = a.arStr
|
|
|
|
return
|
|
}
|
|
|
|
func (r *Resolution) AsSlice() (res [2]int) {
|
|
|
|
res = [2]int{r.w, r.h}
|
|
|
|
return
|
|
}
|
|
|
|
func (r *Resolution) AspectRatio() (ar AspectRatio) {
|
|
|
|
if r == nil {
|
|
return
|
|
}
|
|
|
|
if r.ar == nil {
|
|
r.ar = NewAspectRatio("", r.w, r.h)
|
|
}
|
|
|
|
ar = AspectRatio{
|
|
ar: r.ar.AsFloat(),
|
|
arStr: r.ar.String(),
|
|
arW: r.ar.arW,
|
|
arH: r.ar.arH,
|
|
gcd: r.ar.GCD(),
|
|
res: &Resolution{
|
|
w: r.w,
|
|
h: r.h,
|
|
},
|
|
}
|
|
ar.res.ar = &ar
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
Cmp is a comparison function that can be used for sorting.
|
|
|
|
The return cmp value has the same meaning as in [slices.SortFunc]:
|
|
|
|
If r comes before other, cmp == -1
|
|
If r comes after other, cmp == 1
|
|
If r and other are equal, cmp == 0
|
|
*/
|
|
func (r *Resolution) Cmp(other Resolution) (cmp int) {
|
|
|
|
if r == nil {
|
|
return
|
|
}
|
|
if r.w == other.w && r.h == other.h {
|
|
return
|
|
}
|
|
if r.w == other.w {
|
|
if r.h < other.h {
|
|
cmp = -1
|
|
} else {
|
|
cmp = 1
|
|
}
|
|
} else {
|
|
if r.w < other.w {
|
|
cmp = -1
|
|
} else {
|
|
cmp = 1
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (r *Resolution) Equals(other Resolution) (isEqual bool) {
|
|
|
|
isEqual = r.Cmp(other) == 0
|
|
|
|
return
|
|
}
|
|
|
|
func (r *Resolution) Inverted() (i *Resolution) {
|
|
|
|
i = &Resolution{
|
|
w: r.h,
|
|
h: r.w,
|
|
}
|
|
i.ar = &AspectRatio{
|
|
res: i,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (r *Resolution) String() (s string) {
|
|
|
|
s = fmt.Sprintf("%dx%d", r.w, r.h)
|
|
|
|
return
|
|
}
|
|
|
|
var (
|
|
resolutions [][2]int = [][2]int{
|
|
|
|
/*
|
|
Common
|
|
*/
|
|
|
|
// qqVGA ("Quarter-QVGA")
|
|
[2]int{120, 160}, // 3:4
|
|
[2]int{160, 120}, // 4:3, Standard qqVGA
|
|
[2]int{160, 128}, // 5:4
|
|
|
|
[2]int{320, 240}, // ???, qVGA ("Quarter VGA") (commonly inverted as 240x320 on mobile)
|
|
|
|
// HQVGA ("Half-qVGA")
|
|
[2]int{160, 240}, // ???
|
|
[2]int{240, 160}, // ???, Standard HQVGA
|
|
|
|
[2]int{240, 376}, // 3:2, WQVGA ("Wide qVGA")
|
|
[2]int{240, 432}, // 3:2, WQVGA
|
|
[2]int{360, 240}, // 3:2, WQVGA
|
|
[2]int{384, 240}, // 16:10 (8:5), WQVGA
|
|
[2]int{400, 240}, // 5:3, WQVGA
|
|
[2]int{426, 240}, // ???, WQVGA
|
|
[2]int{428, 240}, // ???, WQVGA
|
|
[2]int{432, 240}, // 18:10, WQVGA
|
|
[2]int{480, 240}, // ???, WQVGA
|
|
|
|
[2]int{480, 270}, // ???, WQVGA / HVGA ("Half-Size VGA")
|
|
[2]int{480, 272}, // ???, WQXGA / HVGA
|
|
|
|
[2]int{480, 320}, // 3:2, Standard HVGA
|
|
[2]int{480, 360}, // 4:3, HVGA
|
|
[2]int{640, 240}, // 8:3, HVGA
|
|
|
|
[2]int{640, 480}, // 4:3, VGA ("Video Graphics Array")
|
|
|
|
[2]int{640, 384}, // 15:9 (5:3), WVGA (WGA) ("Wide VGA")
|
|
[2]int{720, 480}, // 15:10 (3:2), WVGA
|
|
[2]int{768, 480}, // 16:10 (8:5), WVGA
|
|
[2]int{800, 450}, // 16:9, WVGA
|
|
[2]int{800, 480}, // 15:9 (5:3), Standard WVGA
|
|
[2]int{848, 480}, // ???, Standard WVGA
|
|
[2]int{852, 480}, // ???, Standard WVGA
|
|
[2]int{853, 480}, // ???, WVGA
|
|
|
|
[2]int{854, 480}, // ???, Full WVGA
|
|
|
|
[2]int{640, 400}, // ???, SVGA ("Super VGA") / UVGA ("Ultra VGA") / XVGA ("Extended VGA")
|
|
[2]int{640, 480}, // ???, SVGA / UVGA / XVGA
|
|
[2]int{800, 600}, // ???, Standard SVGA / UVGA
|
|
[2]int{830, 624}, // 4:3, SVGA / UVGA
|
|
|
|
[2]int{960, 640}, // 3:2, DVGA ("Double VGA") ("Retina")
|
|
[2]int{1136, 640}, // 16:9, DVGA (iPhone 5 variant)
|
|
|
|
[2]int{1024, 576}, // 16:9, WSVGA ("Wide SVGA")
|
|
[2]int{1024, 600}, // 128:75, WSVGA
|
|
|
|
[2]int{1024, 768}, // 4:3, XGA ("Extended Graphics Array") / SVGA
|
|
|
|
[2]int{1120, 832}, // 35:26, XGA+ ("Extended Graphics Array Plus")
|
|
[2]int{1152, 864}, // 4:3, Standard XGA+
|
|
[2]int{1152, 870}, // 192:145, XGA+
|
|
[2]int{1152, 900}, // 32:25, XGA+
|
|
|
|
[2]int{1152, 768}, // 16:9, WXGA ("Wide XGA")
|
|
[2]int{1280, 720}, // 16:9, WXGA
|
|
[2]int{1280, 768}, // 15:9, Standard WXGA
|
|
[2]int{1280, 800}, // 16:10, Standard WXGA
|
|
|
|
[2]int{1280, 854}, // ???, WXGA+ ("Wide XGA Plus")
|
|
|
|
[2]int{1360, 768}, // ???, FWXGA ("Full WXGA")
|
|
[2]int{1366, 768}, // ???, Standard WXGA / FWXGA
|
|
|
|
[2]int{1280, 960}, // 4:3, QuadVGA / SXVGA ("Super XVGA") / SXGA-
|
|
|
|
[2]int{1280, 1024}, // 5:4, SXGA ("Super XGA") / SVGA
|
|
|
|
[2]int{1360, 768}, // ???, FWXGA
|
|
[2]int{1366, 768}, // ???, WXGA
|
|
|
|
[2]int{1400, 1050}, // 4:3, SXGA+ ("Super XGA Plus")
|
|
|
|
[2]int{1440, 900}, // 16:10, WXGA+ / WSXGA ("Wide SXGA")
|
|
|
|
[2]int{1440, 960}, // 3:2, WSXGA
|
|
[2]int{1600, 1024}, // 25:16, WSXGA
|
|
|
|
[2]int{1600, 1200}, // 4:3, UXGA ("Ultra-XGA")
|
|
|
|
[2]int{1680, 1050}, // 16:10, WSXGA+
|
|
|
|
[2]int{1920, 1200}, // 16:10, WUXGA ("Widescreen UXGA")
|
|
|
|
[2]int{2048, 1152}, // 16:9, QWXGA ("Quad-WXGA")
|
|
|
|
[2]int{2048, 1536}, // 4:3, QXGA ("Quad-XGA")
|
|
|
|
[2]int{2560, 1600}, // 16:10, WQXGA ("Wide QXGA")
|
|
|
|
[2]int{2560, 2048}, // 5:4, QSXGA ("Quad-SXGA")
|
|
|
|
[2]int{2800, 2100}, // 4:3, QSXGA+ ("Quad-SXGA Plus")
|
|
|
|
[2]int{2880, 1800}, // 16:10, WQXGA+ ("Wide QXGA Plus")
|
|
|
|
[2]int{3200, 2048}, // 25:16, WQSXGA ("Wide QSXGA")
|
|
|
|
[2]int{3200, 2400}, // 4:3, QUXGA ("Quad Ultra-XGA")
|
|
|
|
[2]int{3840, 2400}, // 16:10, WQUXGA ("Wide QUXGA")
|
|
|
|
[2]int{1136, 640}, // 16:9, Apple variant
|
|
[2]int{1334, 750}, // 16:9, Apple variant
|
|
[2]int{1792, 828}, // ???, Apple variant
|
|
[2]int{2436, 1125}, // ???, Apple variant
|
|
[2]int{2532, 1170}, // ???, Apple variant
|
|
[2]int{2556, 1179}, // ???, Apple variant
|
|
[2]int{2688, 1242}, // ???, Apple variant
|
|
[2]int{2778, 1285}, // ???, Apple variant
|
|
[2]int{2796, 1290}, // ???, Apple variant
|
|
|
|
[2]int{2160, 1080}, // 2:1, Android variant
|
|
[2]int{2220, 1080}, // ???, Android variant
|
|
[2]int{2280, 1080}, // 19:9, Android variant
|
|
[2]int{2400, 1080}, // 20:9, Android variant
|
|
[2]int{2960, 1440}, // ???, Android variant
|
|
[2]int{3040, 1440}, // 19:9, Android variant
|
|
[2]int{3120, 1440}, // ???, Android variant
|
|
|
|
[2]int{2340, 1080}, // ???, Android/Apple variant
|
|
|
|
// Assorted rare res
|
|
[2]int{576, 720}, // 4:5
|
|
[2]int{900, 720}, // 5:4
|
|
[2]int{960, 720}, // 4:3
|
|
[2]int{1200, 720}, // 5:3
|
|
[2]int{1280, 720}, // 16:9
|
|
|
|
[2]int{1280, 524}, // ???
|
|
[2]int{1280, 532}, // ???
|
|
[2]int{1280, 536}, // ???
|
|
[2]int{1280, 540}, // ???
|
|
[2]int{1280, 544}, // ???
|
|
[2]int{1280, 640}, // ???
|
|
[2]int{1280, 674}, // ???
|
|
[2]int{1280, 692}, // ???
|
|
|
|
// 1:1 - Square
|
|
[2]int{1080, 1080}, // Square Standard
|
|
|
|
// 9:16 - Vertical
|
|
[2]int{1080, 1920}, // Standard Mobile
|
|
|
|
// 16:9 - Standard widescreen
|
|
[2]int{1920, 1080}, // 1080p ("Full HD"/"FHD")
|
|
[2]int{2560, 1440}, // 1440p ("Quad HD"/ "QHD")
|
|
[2]int{3840, 2160}, // 2160p ("4k Ultra-HD"/"4k UHD")
|
|
[2]int{7680, 4320}, // 4320p ("8k Ultra-HD"/"8k UHD")
|
|
|
|
// 16:10 - "Computer monitor" widescreen
|
|
[2]int{1920, 1200}, // WUXGA ("Widescreen Ultra Extended Graphics Array"), https://www.corsair.com/us/en/explorer/gamer/monitors/wuxga-resolution-explained/
|
|
[2]int{2560, 1600}, // WQXGA ("Wide Quad Extended Graphics Array"), https://www.lenovo.com/us/en/glossary/wqxga/
|
|
|
|
// 21:9 - Ultrawide
|
|
[2]int{2560, 1080}, // Ultrawide FHD
|
|
[2]int{3440, 1440}, // Ultrawide QHD
|
|
|
|
// 32:9 - Super Ultrawide
|
|
[2]int{5120, 1440},
|
|
|
|
/*
|
|
Cinema
|
|
*/
|
|
// 37:20 - Flat
|
|
[2]int{1998, 1080}, // 2k Flat DCP ("Digital Cinema Package")
|
|
[2]int{3996, 2160}, // 4k Flat DCP
|
|
|
|
// 1024:429 - Scope
|
|
[2]int{2048, 858}, // 2k Scope DCP
|
|
[2]int{4096, 1716}, // 4k Scope DCP
|
|
|
|
// 256:135 - Full Container
|
|
[2]int{2048, 1080}, // 2k Full Container DCP
|
|
[2]int{4096, 2160}, // 4k Full Container DCP
|
|
}
|
|
)
|
|
|
|
/*
|
|
NewAspectRatio returns a new Aspect Ratio from string form ar.
|
|
|
|
At least one of width or height must be > 0;
|
|
the other can be calculated from ar.
|
|
|
|
If both height and width are > 0, ar is ignored and it will be calculated from those instead.
|
|
If both height and width are <= 0, a will be nil.
|
|
|
|
If ar is an invalid format, a will be nil.
|
|
*/
|
|
func NewAspectRatio(ar string, width, height int) (a *AspectRatio) {
|
|
|
|
var err error
|
|
var arW float64
|
|
var arH float64
|
|
var arStr []string
|
|
|
|
if width <= 0 && height <= 0 {
|
|
return
|
|
} else if width > 0 && height > 0 {
|
|
// ar is ignored.
|
|
a = &AspectRatio{
|
|
ar: float64(width) / float64(height),
|
|
res: &Resolution{
|
|
w: width,
|
|
h: height,
|
|
},
|
|
}
|
|
a.res.ar = a
|
|
} else {
|
|
arStr = strings.SplitN(strings.TrimSpace(ar), ":", 2)
|
|
if len(arStr) != 2 {
|
|
return
|
|
}
|
|
if arH, err = strconv.ParseFloat(strings.TrimSpace(arStr[0]), 64); err != nil {
|
|
return
|
|
}
|
|
if arW, err = strconv.ParseFloat(strings.TrimSpace(arStr[1]), 64); err != nil {
|
|
return
|
|
}
|
|
a = &AspectRatio{
|
|
ar: arW / arH,
|
|
arW: arW,
|
|
arH: arH,
|
|
}
|
|
a.res.ar = a
|
|
if width > 0 {
|
|
a.res.w = width
|
|
a.res.h = int(math.Round(float64(a.res.w) / a.ar))
|
|
} else {
|
|
a.res.h = height
|
|
a.res.w = int(math.Round(float64(a.res.h) * a.ar))
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func NewResolution(width, height int) (r Resolution) {
|
|
|
|
r = Resolution{
|
|
w: width,
|
|
h: height,
|
|
}
|
|
r.ar = &AspectRatio{
|
|
res: &r,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func NewResolutions(resGeom [][2]int) (rsltns []Resolution) {
|
|
|
|
var idx int
|
|
|
|
rsltns = make([]Resolution, len(resGeom))
|
|
for idx = range resGeom {
|
|
rsltns[idx] = NewResolution(resGeom[idx][0], resGeom[idx][1])
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func SortAspectRatios(ar []AspectRatio) (sorted []AspectRatio) {
|
|
|
|
slices.SortStableFunc(
|
|
ar,
|
|
func(a, b AspectRatio) (cmp int) {
|
|
cmp = a.Cmp(b)
|
|
return
|
|
},
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
func SortResolutions(res []Resolution) {
|
|
|
|
slices.SortStableFunc(
|
|
res,
|
|
func(a, b Resolution) (cmp int) {
|
|
cmp = a.Cmp(b)
|
|
return
|
|
},
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
func main() {
|
|
|
|
var ok bool
|
|
var idx int
|
|
var arStr string
|
|
// var ars []string
|
|
var ar AspectRatio
|
|
var res Resolution
|
|
var rsltns []Resolution
|
|
var uniqAr mapset.Set[AspectRatio] = mapset.NewSet[AspectRatio]()
|
|
var uniqRes mapset.Set[[2]int] = mapset.NewSet[[2]int](resolutions...)
|
|
var arMap map[string]mapset.Set[Resolution] = make(map[string]mapset.Set[Resolution])
|
|
|
|
rsltns = NewResolutions(uniqRes.ToSlice())
|
|
SortResolutions(rsltns)
|
|
|
|
for idx, res = range rsltns {
|
|
uniqAr.Add(res.AspectRatio())
|
|
ar = res.AspectRatio()
|
|
arStr = ar.String()
|
|
if _, ok = arMap[arStr]; !ok {
|
|
arMap[arStr] = mapset.NewSet[Resolution]()
|
|
}
|
|
arMap[arStr].Add(res)
|
|
fmt.Printf("#%d: %s (%s)\n", idx, res.String(), ar.FloatString())
|
|
}
|
|
|
|
fmt.Println("\n====\n")
|
|
/*
|
|
for _, aspRatStr = range aspRatKeys {
|
|
fmt.Println(aspRatStr)
|
|
slices.SortStableFunc(
|
|
aspRatRes[aspRatStr],
|
|
func(resA, resB [2]int) (cmp int) {
|
|
// default cmp is 0, which means fully equal
|
|
if resA[0] == resB[0] {
|
|
if resA[1] > resB[1] {
|
|
cmp = 1
|
|
} else {
|
|
cmp = -1
|
|
}
|
|
} else {
|
|
if resA[0] > resB[0] {
|
|
cmp = 1
|
|
} else {
|
|
cmp = -1
|
|
}
|
|
}
|
|
return
|
|
},
|
|
)
|
|
for _, res = range aspRatRes[aspRatStr] {
|
|
fmt.Printf("\t%dx%d\n", res[0], res[1])
|
|
}
|
|
}
|
|
*/
|
|
}
|