commit 3ba9c542bb2db5d5ffe948ce00a36d74e181c641 Author: sys4-fr Date: Thu Dec 13 21:25:14 2018 +0100 Remplissage du dépôt diff --git a/.hg_archival.txt b/.hg_archival.txt new file mode 100644 index 0000000..59f825e --- /dev/null +++ b/.hg_archival.txt @@ -0,0 +1,6 @@ +repo: 09eecdf0f6536402038ec3167b37def72e380b40 +node: 9957de6f53674924478b884100a42b8dec833532 +branch: default +latesttag: null +latesttagdistance: 29 +changessincelatesttag: 29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..96ba8c7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Sascha L. Teichmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..664e439 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# MTAutocolors + +A tool to generate a colors.txt from the registered nodes of a Minetest server. +To have a colors.txt for Minetest mapping for example with [MTSatellite](https://bitbucket.org/s_l_teichmann/mtsatellite) +you need a colors.txt file. You can grab one from [VanessaE](http://digitalaudioconcepts.com/vanessa/hobbies/minetest/colors.txt) +or build one yourself. + +gravgun wrote the generator [automappercolors](https://github.com/MinetestForFun/minetest-minetestforfun-server/tree/master/mods/automappercolors). +MTAutocolors is a slightly extended version of this generator being much faster, too (The image processor +is written in Go instead of Python). It consists of two parts: + +1. The included [mod](https://bitbucket.org/s_l_teichmann/mtautocolors/src/default/mods/automappercolors/) to be installed into your minetest + server to extract the needed node informations. + +2. The program `mtautocolors` to generate the final colors.txt file from the + informations gathered by the mod. + +## Build + +Your need a [Go](http://golang.org/) 1.8 or later [installation](https://golang.org/dl/) to compile the program. +Go 1.9.2+ is recommended as there is a known issue with decoding images in earlier versions. +Mercurial and Git have to be install installed. + + $ go get -u bitbucket.org/s_l_teichmann/mtautocolors/cmd/mtautocolors + +will result in a binary `mtautocolors` to be put into your PATH. + +## Usage + +Install the [mod](https://bitbucket.org/s_l_teichmann/mtautocolors/src/default/mods/automappercolors/) into your +Minetest server. Start your game. Now you can type `/amcdumpnodes` as a chat command in the Minetest client. This +will result in a file `amc_nodes.txt` in the data directory of your world. Once created you can deactivate the +mod until you install or remove mods or update the game in a way which changes the registered nodes. +`mtautocolors` needs a few arguments. The path to the `amc_nodes.txt` file generated with the mod and +paths to the images of your minetest server. This would be the root of the minetest server and/or the folder +where you have the actual minetest game. Something like this (adjusted to your setting): + + $ mtautocolors -predefined=/predefined.json \ + /home/xyz/minetest/worlds/colors/amc_nodes.txt \ + /home/xyz/minetest/ /home/xyz/minetest_game > colors.txt + +There is an option to overwrite the machine generated behavior for certain kinds +of nodes with a JSON file. Look at [predefined.json](https://bitbucket.org/s_l_teichmann/mtautocolors/src/default/predefined.json) +to get the idea. Basically its a list of entries with a regular expression matching the node name +and a tuple of RGBA values which should be used instead of the machine generated. + +Transparency: Some nodes should be transparent like glass or water. `mtautocolors` only generates +a alpha channel for nodes with of certain draw types. By default this are the ones which drawtype contains 'glasslike' or 'liquid'. +This can be overwritten with the `-transparent=` flag. + +This is Free Software under the terms of the MIT license. See [LICENSE](https://bitbucket.org/s_l_teichmann/mtautocolors/src/default/LICENSE) +for details. +gravgun's original is covered by the WTFPL license. See [LICENSE](https://bitbucket.org/s_l_teichmann/mtautocolors/src/default/mods/automappercolors/LICENSE) +for details, too. diff --git a/cmd/mtautocolors/images.go b/cmd/mtautocolors/images.go new file mode 100644 index 0000000..f5d75f1 --- /dev/null +++ b/cmd/mtautocolors/images.go @@ -0,0 +1,87 @@ +// Copyright 2015 by Sascha L. Teichmann +// Use of this source code is governed by the MIT license +// that can be found in the LICENSE file. + +package main + +import ( + "bufio" + "fmt" + "image" + "image/color" + "image/draw" + "os" + + _ "image/jpeg" + _ "image/png" +) + +func loadRGBA(filename string) (*image.RGBA, error) { + + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + var img image.Image + if img, _, err = image.Decode(bufio.NewReader(file)); err != nil { + return nil, fmt.Errorf("Decoding '%s' failed: %s", filename, err) + } + + if rgba, ok := img.(*image.RGBA); ok { + return rgba, nil + } + bounds := img.Bounds() + rgba := image.NewRGBA(bounds) + draw.Draw(rgba, bounds, img, image.ZP, draw.Src) + + return rgba, nil +} + +func averageColor(filename string) (color.Color, error) { + + img, err := loadRGBA(filename) + if err != nil { + return nil, err + } + + bounds := img.Bounds() + if bounds.Empty() { + return color.Black, nil + } + y := img.PixOffset(bounds.Min.X, bounds.Min.Y) + yEnd := img.PixOffset(bounds.Min.X, bounds.Max.Y) + w := bounds.Dx() * 4 + pix := img.Pix + + var r, g, b, a uint64 + + for ; y < yEnd; y += img.Stride { + for pos, end := y, y+w; pos < end; pos += 4 { + pa := uint64(pix[pos+3]) + r += pa * uint64(pix[pos]) + g += pa * uint64(pix[pos+1]) + b += pa * uint64(pix[pos+2]) + a += pa + } + } + + r /= 255 + g /= 255 + b /= 255 + + if s := a / 255; s > 0 { + r /= s + g /= s + b /= s + } + + col := color.RGBA{ + R: uint8(r), + G: uint8(g), + B: uint8(b), + A: uint8(a / uint64(bounds.Dx()*bounds.Dy()))} + + return &col, nil +} diff --git a/cmd/mtautocolors/main.go b/cmd/mtautocolors/main.go new file mode 100644 index 0000000..3cc85e2 --- /dev/null +++ b/cmd/mtautocolors/main.go @@ -0,0 +1,237 @@ +// Copyright 2015 by Sascha L. Teichmann +// Use of this source code is governed by the MIT license +// that can be found in the LICENSE file. + +package main + +import ( + "bufio" + "flag" + "fmt" + "image/color" + "io" + "log" + "os" + "path/filepath" + "regexp" + "runtime" + "sort" + "strings" + "sync" +) + +var nodesSplit = regexp.MustCompile(" +") + +func usage() { + fmt.Fprintf(os.Stderr, + "Usage: %s [] [ ...]\n", + os.Args[0]) + fmt.Fprintln(os.Stderr, "Options:") + flag.PrintDefaults() +} + +func main() { + var predef string + var transparent string + var workers int + + flag.Usage = usage + flag.StringVar(&predef, "predefined", "", "predefined colors") + flag.StringVar(&predef, "p", "", "predefined colors (shorthand)") + flag.StringVar(&transparent, "transparent", "glasslike,liquid", "transparent nodes") + flag.StringVar(&transparent, "t", "glasslike,liquid", "transparent nodes (shorthand)") + flag.IntVar(&workers, "workers", 0, "number of image processing workers") + flag.IntVar(&workers, "w", 0, "number of image processing workers (shorthand)") + flag.Parse() + + nargs := flag.NArg() + if nargs < 1 { + usage() + os.Exit(1) + } + + var err error + var predefs PredefCols + + if predef != "" { + if predefs, err = LoadPredefCols(predef); err != nil { + log.Fatalf("Cannot load predefined colors: %s\n", err) + } + } + + var roots []string + if nargs > 1 { + roots = flag.Args()[1:] + } else { + roots = []string{"."} + } + + files, err := buildFileIndex(roots) + if err != nil { + log.Fatalf("error while building file index: %s\n", err) + } + + drawTypes := strings.Split(transparent, ",") + + if err = process(flag.Arg(0), drawTypes, files, predefs, workers); err != nil { + log.Fatalf("error while generating colors: %s\n", err) + } +} + +func contains(haystack []string, needle string) bool { + for _, straw := range haystack { + if strings.Contains(needle, straw) { + return true + } + } + return false +} + +type outLine struct { + name string + col color.Color + alpha bool +} + +func asByte(x uint32) byte { + return byte(x >> 8) +} + +func (ol *outLine) print(out io.Writer) { + r, g, b, a := ol.col.RGBA() + ba := asByte(a) + if ol.alpha && ba < 255 { + fmt.Fprintf(out, "%s %d %d %d %d\n", + ol.name, asByte(r), asByte(g), asByte(b), ba) + } else { + fmt.Fprintf(out, "%s %d %d %d\n", + ol.name, asByte(r), asByte(g), asByte(b)) + } +} + +func process( + nodesFile string, + drawTypes []string, + files map[string]string, + predefs PredefCols, + workers int) error { + + file, err := os.Open(nodesFile) + if err != nil { + return err + } + defer file.Close() + + lineCh := make(chan []string) + + if workers < 1 { + workers = runtime.NumCPU() + } + + var outLineMu sync.Mutex + var outLines []outLine + + var wg sync.WaitGroup + + for i := 0; i < workers; i++ { + wg.Add(1) + go func() { + defer wg.Done() + lines: + for line := range lineCh { + name, drawType, texture := line[0], line[1], line[2] + + var c color.Color + switch col := predefs.findCol(name); { + case col != nil && col.Complete(): + c = col + case col == nil || !col.Complete(): + tfile := files[texture] + if tfile == "" { + log.Printf("WARN: node '%s' missing texture '%s'\n", name, texture) + continue lines + } + avg, err := averageColor(tfile) + if err != nil { + log.Printf("WARN: node '%s' defect image: %s\n", name, err) + continue lines + } + if col != nil { + c = col.Apply(avg) + } else { + c = avg + } + } + alpha := contains(drawTypes, drawType) + outLineMu.Lock() + outLines = append(outLines, outLine{ + name: name, + col: c, + alpha: alpha, + }) + outLineMu.Unlock() + } + }() + } + + scanner := bufio.NewScanner(file) + for lineNo := 1; scanner.Scan(); lineNo++ { + parts := nodesSplit.Split(scanner.Text(), 3) + if len(parts) < 3 { + log.Printf("WARN: line %d too short.\n", lineNo) + } else { + lineCh <- parts + } + } + close(lineCh) + wg.Wait() + if err := scanner.Err(); err != nil { + return err + } + + // To make it more deterministic. + sort.Slice(outLines, func(i, j int) bool { + return outLines[i].name < outLines[j].name + }) + + out := bufio.NewWriter(os.Stdout) + for i := range outLines { + outLines[i].print(out) + } + return out.Flush() +} + +func buildFileIndex(roots []string) (map[string]string, error) { + index := make(map[string]string) + + acceptedExts := map[string]bool{ + ".png": true, + ".jpg": true, + ".jpeg": true} + + walkFn := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.Mode().IsRegular() { + return nil + } + name := info.Name() + if !acceptedExts[strings.ToLower(filepath.Ext(name))] { + return nil + } + if _, found := index[name]; !found { + index[name] = path + } else { + log.Printf("WARN: more than one file for '%s'\n", name) + } + return nil + } + + for _, root := range roots { + if err := filepath.Walk(root, walkFn); err != nil { + return nil, err + } + } + return index, nil +} diff --git a/cmd/mtautocolors/predefined.go b/cmd/mtautocolors/predefined.go new file mode 100644 index 0000000..452230e --- /dev/null +++ b/cmd/mtautocolors/predefined.go @@ -0,0 +1,116 @@ +// Copyright 2015 by Sascha L. Teichmann +// Use of this source code is governed by the MIT license +// that can be found in the LICENSE file. + +package main + +import ( + "bufio" + "encoding/json" + "image/color" + "os" + "regexp" + "strconv" +) + +type Component struct { + Value uint8 + Used bool +} + +type Expr struct{ *regexp.Regexp } + +type Col struct { + E Expr `json:"expr"` + R Component `json:"r"` + G Component `json:"g"` + B Component `json:"b"` + A Component `json:"a"` +} + +type PredefCols []Col + +func (e *Expr) UnmarshalJSON(data []byte) error { + unquoted := string(data[1 : len(data)-1]) + expr, err := regexp.Compile(unquoted) + if err != nil { + return err + } + *e = Expr{expr} + return nil +} + +func (c *Component) UnmarshalJSON(data []byte) error { + v, err := strconv.ParseUint(string(data), 10, 8) + if err != nil { + return err + } + c.Value = uint8(v) + c.Used = true + return nil +} + +func LoadPredefCols(filename string) (PredefCols, error) { + + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + decoder := json.NewDecoder(bufio.NewReader(file)) + + var predef PredefCols + if err = decoder.Decode(&predef); err != nil { + return nil, err + } + + return predef, nil +} + +func (pd *PredefCols) findCol(name string) *Col { + for i, n := 0, len(*pd); i < n; i++ { + if (*pd)[i].E.MatchString(name) { + return &(*pd)[i] + } + } + return nil +} + +func (c *Col) Complete() bool { + return c.R.Used && c.G.Used && c.B.Used && c.A.Used +} + +func (c *Col) RGBA() (r, g, b, a uint32) { + r = uint32(c.R.Value) + r |= r << 8 + g = uint32(c.G.Value) + g |= g << 8 + b = uint32(c.B.Value) + b |= b << 8 + a = uint32(c.A.Value) + a |= a << 8 + return +} + +func (c *Col) Apply(other color.Color) color.Color { + r, g, b, a := other.RGBA() + x := color.RGBA{ + R: uint8(r >> 8), + G: uint8(g >> 8), + B: uint8(b >> 8), + A: uint8(a >> 8)} + if c.R.Used { + x.R = c.R.Value + } + if c.G.Used { + x.G = c.G.Value + } + if c.B.Used { + x.B = c.B.Value + } + if c.A.Used { + x.A = c.A.Value + } + return &x +} diff --git a/maps_mt_sha_bang_de_predefined.json b/maps_mt_sha_bang_de_predefined.json new file mode 100644 index 0000000..b3785b3 --- /dev/null +++ b/maps_mt_sha_bang_de_predefined.json @@ -0,0 +1,16 @@ +[ + { "expr": "^default:(river_water_.+|water.+)", "a": 128}, + { "expr": "^default:glass", "a": 128}, + { "expr": "^pumpkin_face_light", "r": 255, "g": 255, "b": 36, "a": 255}, + { "expr": "^default:torch", "r": 255, "g": 255, "b": 0, "a": 255}, + { "expr": "^farming:cotton_(6|7|8)", "r": 255, "g": 255, "b": 255}, + { "expr": "^default:junglewood", "r": 79, "g": 57, "b": 26, "a": 255}, + { "expr": "^default:(desertstone|desert_stone)", "r": 149, "g": 99, "b": 80, "a": 255}, + { "expr": "^pipeworks:tube_.+", "r": 190, "g": 190, "b": 190, "a": 255}, + { "expr": "^pipeworks:(priority|crossing|detector)_tube_.+", "r": 190, "g": 190, "b": 190, "a": 255}, + { "expr": "^tnt:gunpowder_burning$", "r": 122, "g": 59, "b": 1, "a": 255}, + { "expr": "^tnt:gunpowder$", "r": 0, "g": 0, "b": 0 , "a": 255}, + { "expr": "^default:nyancat_rainbow$", "r": 102, "g": 50, "b": 255, "a": 255}, + { "expr": "^default:nyancat$", "r": 255, "g": 153, "b": 255, "a": 255 }, + { "expr": "^default:mossycobble", "r": 75, "g": 100, "b": 71, "a": 255 } +] diff --git a/mods/automappercolors/LICENSE b/mods/automappercolors/LICENSE new file mode 100644 index 0000000..d6a786d --- /dev/null +++ b/mods/automappercolors/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2015 gravgun + 14 rue de Plaisance, 75014 Paris, France + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/mods/automappercolors/init.lua b/mods/automappercolors/init.lua new file mode 100644 index 0000000..b6f3206 --- /dev/null +++ b/mods/automappercolors/init.lua @@ -0,0 +1,50 @@ +-- Automappercolors by gravgun, modified by s-l-teichmann +-- WTFPL + +function amc_dumpnodes() + local fd, err = io.open(minetest.get_worldpath()..'/amc_nodes.txt', 'wb') + if not fd then + return 0, err + end + local n = 0 + for name, def in pairs(minetest.registered_nodes) do + if def.drawtype ~= 'airlike' then + local tile = def.tiles or def.tile_images + if type(tile) == 'table' then + tile = tile[1] + if type(tile) == 'table' then + tile = tile.name + end + end + if tile ~= nil then + tile = (tile .. '^'):match('([a-zA-Z0-9\\._-]-)^') + fd:write(name .. ' ' .. def.drawtype .. ' ' .. tile .. '\n') + n = n + 1 + end + end + end + fd:close() + return n, "done" +end + +minetest.register_chatcommand("amcdumpnodes", { + params = "", + description = "", + func = function(plname, param) + local n, msg = amc_dumpnodes() + if n == 0 then + minetest.chat_send_player(plname, 'io.open: ' .. msg) + else + minetest.chat_send_player(plname, n .. " nodes dumped.") + end + end, +}) + +minetest.after(1, function(args) + amc_dumpnodes() + if minetest.setting_getbool("log_mods") then + minetest.log("action", "[automappercolors] nodes dumped") + end +end) + + diff --git a/predefined.json b/predefined.json new file mode 100644 index 0000000..d4967e8 --- /dev/null +++ b/predefined.json @@ -0,0 +1,106 @@ +[ + { + "expr": "^default:([a-z_]*)glass", + "a": 64 + }, + { + "expr": "^default:torch", + "r": 255, + "g": 255, + "b": 0, + "a": 255 + }, + { + "expr": "^default:ice", + "r": 74, + "g": 105, + "b": 159, + "a": 159 + }, + { + "expr": "^default:water_([a-z]+)", + "r": 43, + "g": 97, + "b": 183, + "a": 128 + }, + { + "expr": "^default:dirt_with_grass", + "r": 107, + "g": 134, + "b": 51, + "a": 255 + }, + { + "expr": "^flowers:cotton_plant", + "r": 199, + "g": 218, + "b": 158 + }, + { + "expr": "^flowers:seaweed", + "r": 48, + "g": 114, + "b": 107 + }, + { + "expr": "^flowers:waterlily", + "r": 119, + "g": 166, + "b": 100 + }, + { + "expr": "^flowers:waterlily_225", + "r": 119, + "g": 166, + "b": 100 + }, + { + "expr": "^flowers:waterlily_45", + "r": 119, + "g": 166, + "b": 100 + }, + { + "expr": "^flowers:waterlily_675", + "r": 119, + "g": 166, + "b": 100 + }, + { + "expr": "^flowers:dandelion_white", + "r": 161, + "g": 174, + "b": 149 + }, + { + "expr": "^flowers:dandelion_yellow", + "r": 144, + "g": 138, + "b": 0 + }, + { + "expr": "^flowers:geranium", + "r": 75, + "g": 101, + "b": 84 + }, + { + "expr": "^flowers:rose", + "r": 153, + "g": 9, + "b": 0 + }, + { + "expr": "^flowers:tulip", + "r": 175, + "g": 114, + "b": 0 + }, + { + "expr": "^flowers:viola", + "r": 84, + "g": 90, + "b": 64 + } +]