A tool for archiving & converting scans of postcards, and information about them.
at main 127 lines 3.2 kB view raw
1package types 2 3import ( 4 "fmt" 5 "math" 6 "math/big" 7) 8 9const maxRatioDiff = 0.01 10 11type Size struct { 12 CmWidth *big.Rat `json:"cmW,omitempty"` 13 CmHeight *big.Rat `json:"cmH,omitempty"` 14 PxWidth int `json:"pxW"` 15 PxHeight int `json:"pxH"` 16} 17 18// The fallback surface area of a postcard, if no physical size is provided, but one is needed 19var assumedSurfaceArea = 10.5 * 14.8 20 21// Returns the actual physical dimensions (in cm), or guesses at a physical size 22// based on the assumption that most postcards are 6x4 inches. 23func (s Size) MustPhysical() (float64, float64) { 24 if s.HasPhysical() { 25 w, _ := s.CmWidth.Float64() 26 h, _ := s.CmHeight.Float64() 27 return w, h 28 } 29 30 pxA := float64(s.PxWidth * s.PxHeight) 31 ar := float64(s.PxWidth) / float64(s.PxHeight) 32 res := assumedSurfaceArea / float64(pxA) 33 34 cmH := math.Sqrt(res * pxA / ar) 35 cmW := ar * cmH 36 37 return cmW, cmH 38} 39 40func (s Size) HasPhysical() bool { 41 return s.CmWidth != nil && s.CmHeight != nil 42} 43 44// SetResolution is a helper function for setting the physical dimensions using a Dots Per Centimetre resolution 45func (s *Size) SetResolution(xRes *big.Rat, yRes *big.Rat) { 46 if xRes == nil || yRes == nil { 47 return 48 } 49 50 s.CmWidth = (&big.Rat{}).Quo( 51 big.NewRat(int64(s.PxWidth), 1), 52 xRes, 53 ) 54 s.CmHeight = (&big.Rat{}).Quo( 55 big.NewRat(int64(s.PxHeight), 1), 56 yRes, 57 ) 58} 59 60// Resolution returns the pixels per centimetre 61func (s Size) Resolution() (xRes *big.Rat, yRes *big.Rat) { 62 xRes = (&big.Rat{}).Quo(big.NewRat(int64(s.PxWidth), 1), s.CmWidth) 63 yRes = (&big.Rat{}).Quo(big.NewRat(int64(s.PxHeight), 1), s.CmHeight) 64 return 65} 66 67type Orientation string 68 69const ( 70 OrientationLandscape Orientation = "landscape" 71 OrientationPortrait Orientation = "portrait" 72 OrientationSquare Orientation = "square" 73) 74 75// Returns the most probable orientation of the size. Within 5mm/5px of square is considered square 76func (s Size) Orientation() Orientation { 77 if s.HasPhysical() { 78 diff, _ := new(big.Rat).Sub(s.CmWidth, s.CmHeight).Float64() 79 if math.Abs(diff) <= 0.5 { 80 return OrientationSquare 81 } 82 } else { 83 diff := math.Abs(float64(s.PxWidth - s.PxHeight)) 84 if math.Abs(diff) <= 5 { 85 return OrientationSquare 86 } 87 } 88 89 if s.PxWidth > s.PxHeight { 90 return OrientationLandscape 91 } 92 return OrientationPortrait 93} 94 95// SimilarPhysical compares two physical sizes and returns true if their dimensions are within ~1% of each other 96func (s Size) SimilarPhysical(other Size, flip Flip) bool { 97 if !s.HasPhysical() || !other.HasPhysical() { 98 return true 99 } 100 101 if flip.IsHeteroriented() { 102 return similar(s.CmWidth, other.CmHeight) && similar(s.CmHeight, other.CmWidth) 103 } else { 104 return similar(s.CmWidth, other.CmWidth) && similar(s.CmHeight, other.CmHeight) 105 } 106} 107 108func similar(a, b *big.Rat) bool { 109 ratio, _ := big.NewRat(1, 1).Quo(a, b).Float64() 110 return math.Abs(1-ratio) <= maxRatioDiff 111} 112 113func (s Size) String() string { 114 pxSize := fmt.Sprintf("%dpx x %dpx", s.PxWidth, s.PxHeight) 115 if !s.HasPhysical() { 116 return pxSize 117 } 118 119 fw, _ := s.CmWidth.Float64() 120 fh, _ := s.CmHeight.Float64() 121 122 return fmt.Sprintf( 123 "%.1fcm x %.1fcm (%dpx x %dpx)", 124 fw, fh, 125 s.PxWidth, s.PxHeight, 126 ) 127}