Skip to content

Instantly share code, notes, and snippets.

@wispborne
Last active September 11, 2020 22:49
Show Gist options
  • Save wispborne/58b78660e52a83538afe99b3a4186e23 to your computer and use it in GitHub Desktop.
Save wispborne/58b78660e52a83538afe99b3a4186e23 to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"fmt"
"image"
"image/color"
_ "image/jpeg"
_ "image/png"
"io/ioutil"
"math"
"os"
"path"
"path/filepath"
"strings"
)
/**
* v1.1.0
*
* Original Python script by Dark Revenant.
* Transcoded to Golang and edited to show more info by Wisp.
*
* Dependencies:
*/
var dirPath, errorGettingDir = os.Getwd()
var debug = false
func main() {
finalReadout := ""
totalBytes := 0
check(errorGettingDir)
modFolders, err := ioutil.ReadDir(dirPath)
check(err)
fmt.Println("Current dir: " + dirPath)
// Iterate over mod folders
for _, modFolder := range modFolders {
fmt.Println("Mod: " + modFolder.Name())
var fileRelativePathList []string
err := filepath.Walk(modFolder.Name(),
func(filePath string, info os.FileInfo, err error) error {
if !info.IsDir() {
fileRelativePathList = append(fileRelativePathList, filePath)
}
return nil
})
check(err)
totalBytesForMod := float64(0)
//didAlreadyCountBackgroundImage := false
for _, relativeFilePath := range fileRelativePathList {
absoluteFilePath := path.Join(dirPath, relativeFilePath)
if strings.Contains(relativeFilePath, "_CURRENTLY_UNUSED") {
fmt.Printf("Skipping unused file %s\n", relativeFilePath)
continue
}
reader, err := os.Open(absoluteFilePath)
check(err)
img, _, err := image.Decode(reader)
if err != nil {
if debug {
fmt.Printf("Skipping %s (%s)\n", relativeFilePath, err)
}
continue
}
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
bitsPerChannel := 0
numColorChannels := 0
doesModContainABackgroundImage := false
hasAlpha := !Opaque(img)
bytesToSubtract := 0
switch img.ColorModel() {
case
color.Alpha16Model:
bitsPerChannel = 16
numColorChannels = 0
case color.Gray16Model:
bitsPerChannel = 16
numColorChannels = 1
case color.GrayModel:
bitsPerChannel = 8
numColorChannels = 1
case color.AlphaModel:
bitsPerChannel = 8
numColorChannels = 0
case color.CMYKModel:
bitsPerChannel = 8
numColorChannels = 4
case color.NRGBAModel:
bitsPerChannel = 8
numColorChannels = 3
case color.NYCbCrAModel:
bitsPerChannel = 8
numColorChannels = 3
case color.RGBAModel:
bitsPerChannel = 8
numColorChannels = 3
case color.NRGBA64Model:
bitsPerChannel = 16
numColorChannels = 3
case color.RGBA64Model:
bitsPerChannel = 16
numColorChannels = 3
case color.YCbCrModel:
bitsPerChannel = 8
numColorChannels = 3
}
numChannels := numColorChannels
if bitsPerChannel == 0 || numColorChannels == 0 {
fmt.Printf("\nUnknown image format: %s\n", absoluteFilePath)
// Use most common values so we can continue
bitsPerChannel = 8
numColorChannels = 3
}
// Note: This is not entirely correct.
// If the image has an alpha channel, but doesn't use it (image entirely opaque)
// then the alpha channel won't be counted, but it should be.
// In practice, this only seems to cause mod counts to be off by a few kilobytes.
if hasAlpha {
numChannels = numChannels + 1
}
// tex height and width rounds the image width + height up to the next power of two
texWidth := 1
for texWidth < width {
texWidth = texWidth * 2
}
texHeight := 1
for texHeight < height {
texHeight = texHeight * 2
}
// Increase memory use due to mipmapping
multiplier := float64(4) / 3
if strings.Contains(relativeFilePath, "backgrounds") {
// If mod adds a larger-than-vanilla background, count it as increasing VRAM, but only once
// because only one background is loaded at a time
if doesModContainABackgroundImage {
fmt.Printf("Skipping backgrounds after the first in the mod %s\n", relativeFilePath)
continue
}
// If file is a standard size background, then it should not increase VRAM use
// because only one background is loaded at a time
if width <= 2048 {
fmt.Printf("Skipped vanilla-sized (%dpx width) background %s\n", width, relativeFilePath)
continue
}
// Number of bytes in a vanilla background image
// Only count any excess toward the mod's VRAM hit
bytesToSubtract = 12582912
multiplier = float64(1)
}
bytesPerPixel := float64(bitsPerChannel*numChannels) / float64(8)
bytes := int(math.Ceil(float64(texWidth)*float64(texHeight)*bytesPerPixel*multiplier)) - bytesToSubtract
totalBytesForMod = float64(totalBytesForMod + float64(bytes))
totalBytes = totalBytes + bytes
fmt.Printf("%s: TexHeight: %d, TexWidth: %d, Channels (alpha: %t): %d (Mult=%f)\n"+
" --> %d * %d * %d * %f = %d bytes\n",
relativeFilePath, texHeight, texWidth,
hasAlpha, numChannels, multiplier,
texHeight, texWidth, numChannels, multiplier, bytes)
}
if totalBytesForMod == 0 {
continue
}
modPrintout := fmt.Sprintf("\n%s\n", modFolder.Name())
modPrintout = modPrintout + getSizeBreakdown(totalBytesForMod)
finalReadout = finalReadout + modPrintout
}
fmt.Println(finalReadout)
fmt.Printf("-------------\nTotal Modlist\n%s", getSizeBreakdown(float64(totalBytes)))
bufio.NewReader(os.Stdin).ReadString('\n')
}
func check(e error) {
if e != nil {
bufio.NewReader(os.Stdin).ReadString('\n')
panic(e)
}
}
func getSizeBreakdown(bytes float64) string {
result := fmt.Sprintf("%.0f bytes\n", bytes)
if bytes >= 1024 {
result = result + fmt.Sprintf("%.3f kiB\n", bytes/1024)
}
if bytes >= 1048576 {
result = result + fmt.Sprintf("%.3f MiB\n", bytes/1048576)
}
if bytes >= 1073741824 {
result = result + fmt.Sprintf("%.3f GiB\n", bytes/1073741824)
}
return result
}
// from https://stackoverflow.com/a/44105659/1622788
func Opaque(im image.Image) bool {
// Check if image has Opaque() method:
if oim, ok := im.(interface {
Opaque() bool
}); ok {
return oim.Opaque() // It does, call it and return its result!
}
// No Opaque() method, we need to loop through all pixels and check manually:
rect := im.Bounds()
for y := rect.Min.Y; y < rect.Max.Y; y++ {
for x := rect.Min.X; x < rect.Max.X; x++ {
if _, _, _, a := im.At(x, y).RGBA(); a != 0xffff {
return false // Found a non-opaque pixel: image is non-opaque (transparent)
}
}
}
return true // All pixels are opaque, so is the image
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment