Initial commit
|
@ -0,0 +1 @@
|
||||||
|
textures
|
|
@ -0,0 +1,2 @@
|
||||||
|
globals = {"texgen"}
|
||||||
|
read_globals = {"minetest", "modlib", "Settings"}
|
|
@ -0,0 +1,111 @@
|
||||||
|
# Texture Generator (`texgen`)
|
||||||
|
|
||||||
|
Dynamically generates a texture pack for your current game in the `textures` directory.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Compatible with virtually all mods and games
|
||||||
|
* Easy texture pack generation, no complex installation
|
||||||
|
* Server-side (mod-based) texture pack; textures can be accessed by other mods
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Just fire up the mod and configure it to your liking. It might throw an error message as it updates its `mod.conf` to depend on all enabled mods. Simply retry after that.
|
||||||
|
|
||||||
|
### Downloading palettes
|
||||||
|
|
||||||
|
You can use the `/download_palette <url>` chatcommand to download a PNG palette file, for example `/download_palette https://lospec.com/palette-list/zughy-32-1x.png`.
|
||||||
|
|
||||||
|
See `/help download_palette` for details on its usage.
|
||||||
|
|
||||||
|
Requires the `server` priv. Only available if `texgen` is added to `secure.http_mods`.
|
||||||
|
|
||||||
|
**WARNING: Enabling this feature poses a minor security risk.**
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
<!--modlib:conf:2-->
|
||||||
|
### `average`
|
||||||
|
|
||||||
|
Replace each texture with a single pixel of its weighted average RGB color
|
||||||
|
|
||||||
|
* Type: boolean
|
||||||
|
* Default: `false`
|
||||||
|
|
||||||
|
### `invert`
|
||||||
|
|
||||||
|
Invert the RGB colors
|
||||||
|
|
||||||
|
* Type: boolean
|
||||||
|
* Default: `false`
|
||||||
|
|
||||||
|
### `monochrome`
|
||||||
|
|
||||||
|
Convert RGB to monochrome (greyscale)
|
||||||
|
|
||||||
|
* Type: boolean
|
||||||
|
* Default: `false`
|
||||||
|
|
||||||
|
### `palette`
|
||||||
|
|
||||||
|
#### `dithering`
|
||||||
|
|
||||||
|
Dithering method to use
|
||||||
|
|
||||||
|
* Type: string
|
||||||
|
* Default: `floyd_steinberg`
|
||||||
|
* Possible values:
|
||||||
|
* sierra
|
||||||
|
* stucke
|
||||||
|
* sierra_lite
|
||||||
|
* jarvis_judice_ninke
|
||||||
|
* floyd_steinberg
|
||||||
|
* none
|
||||||
|
* two_row_sierra
|
||||||
|
* burkes
|
||||||
|
* atkinson
|
||||||
|
|
||||||
|
#### `name`
|
||||||
|
|
||||||
|
Name of the palette to use (without extension)
|
||||||
|
|
||||||
|
* Type: string
|
||||||
|
* Default: `apollo`
|
||||||
|
* Possible values:
|
||||||
|
* soggy-newspapers
|
||||||
|
* apollo
|
||||||
|
* zughy-32
|
||||||
|
* aap-64
|
||||||
|
* pico-8
|
||||||
|
* resurrect-64
|
||||||
|
|
||||||
|
|
||||||
|
### `saturate`
|
||||||
|
|
||||||
|
Increase or decrease saturation by a factor
|
||||||
|
|
||||||
|
* Type: number
|
||||||
|
* Default: `1`
|
||||||
|
* >= `0.1`
|
||||||
|
* <= `10`
|
||||||
|
<!--modlib:conf-->
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [GitHub](https://github.com/appgurueu/texgen) - sources, issue tracking, contributing
|
||||||
|
* [Discord](https://discord.gg/ysP74by) - discussion, chatting
|
||||||
|
* [Minetest Forum](https://forum.minetest.net/viewtopic.php?f=9&t=28115) - (more organized) discussion
|
||||||
|
* [ContentDB](https://content.minetest.net/packages/LMD/texgen) - releases (downloading from GitHub is recommended)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Code written by Lars Müller (appgurueu) and licensed under the MIT license; builtin Minetest media licensed under various free Creative Commons licenses as well as the Apache 2 license (see `LICENSE.txt` in the `builtin` folder for details and attribution).
|
||||||
|
|
||||||
|
The palettes are (for copyright reasons shuffled) versions of palettes available on [Lospec](https://lospec.com/):
|
||||||
|
|
||||||
|
* Adigun A. Polack's "AAP-64"
|
||||||
|
* AdamCYounis' "Apollo"
|
||||||
|
* Lexaloffle Games' "PICO-8"
|
||||||
|
* Kerrie Lake's "Resurrect 64"
|
||||||
|
* Walking's "Soggy Newspapers"
|
||||||
|
* Zughy's "Zughy 32"
|
|
@ -0,0 +1,18 @@
|
||||||
|
-- Average color, weighted by alpha and calculated in linear RGB colorspace
|
||||||
|
return function(png)
|
||||||
|
-- TODO make colorspecs extend vectors
|
||||||
|
local avg_color = modlib.vector.new{r = 0, g = 0, b = 0}
|
||||||
|
local total_alpha = 0
|
||||||
|
for _, color in ipairs(png.data) do
|
||||||
|
color = modlib.minetest.colorspec.from_number(color)
|
||||||
|
-- Squared average
|
||||||
|
avg_color = modlib.vector.add(avg_color, modlib.vector.multiply_scalar(
|
||||||
|
modlib.vector.pow_scalar({r = color.r, g = color.g, b = color.b}, 2), color.a))
|
||||||
|
total_alpha = total_alpha + color.a
|
||||||
|
end
|
||||||
|
if total_alpha == 0 then total_alpha = 1 end -- Avoid division by zero
|
||||||
|
-- Round & convert back to colorspec
|
||||||
|
avg_color = modlib.minetest.colorspec.new(avg_color:divide_scalar(total_alpha)
|
||||||
|
:apply(math.sqrt):add_scalar(0.5):floor())
|
||||||
|
return avg_color
|
||||||
|
end
|
|
@ -0,0 +1,197 @@
|
||||||
|
|
||||||
|
License of Minetest textures and sounds
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
This applies to textures and sounds contained in the main Minetest
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
||||||
|
http://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
|
||||||
|
textures/base/pack/refresh.png is under the Apache 2 license
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
|
Textures by Zughy are under CC BY-SA 4.0
|
||||||
|
https://creativecommons.org/licenses/by-sa/4.0/
|
||||||
|
|
||||||
|
textures/base/pack/server_public.png is under CC-BY 4.0, taken from Twitter's Twemoji set
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Authors of media files
|
||||||
|
-----------------------
|
||||||
|
Everything not listed in here:
|
||||||
|
Copyright (C) 2010-2012 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
|
||||||
|
ShadowNinja:
|
||||||
|
textures/base/pack/smoke_puff.png
|
||||||
|
|
||||||
|
paramat:
|
||||||
|
textures/base/pack/menu_header.png
|
||||||
|
textures/base/pack/next_icon.png
|
||||||
|
textures/base/pack/prev_icon.png
|
||||||
|
textures/base/pack/clear.png
|
||||||
|
textures/base/pack/search.png
|
||||||
|
|
||||||
|
rubenwardy, paramat:
|
||||||
|
textures/base/pack/start_icon.png
|
||||||
|
textures/base/pack/end_icon.png
|
||||||
|
|
||||||
|
erlehmann:
|
||||||
|
misc/minetest-icon-24x24.png
|
||||||
|
misc/minetest-icon.ico
|
||||||
|
misc/minetest.svg
|
||||||
|
textures/base/pack/logo.png
|
||||||
|
|
||||||
|
JRottm:
|
||||||
|
textures/base/pack/player_marker.png
|
||||||
|
|
||||||
|
srifqi:
|
||||||
|
textures/base/pack/chat_hide_btn.png
|
||||||
|
textures/base/pack/chat_show_btn.png
|
||||||
|
textures/base/pack/joystick_bg.png
|
||||||
|
textures/base/pack/joystick_center.png
|
||||||
|
textures/base/pack/joystick_off.png
|
||||||
|
textures/base/pack/minimap_btn.png
|
||||||
|
|
||||||
|
Zughy:
|
||||||
|
textures/base/pack/cdb_add.png
|
||||||
|
textures/base/pack/cdb_clear.png
|
||||||
|
textures/base/pack/cdb_downloading.png
|
||||||
|
textures/base/pack/cdb_queued.png
|
||||||
|
textures/base/pack/cdb_update.png
|
||||||
|
textures/base/pack/cdb_viewonline.png
|
||||||
|
|
||||||
|
appgurueu:
|
||||||
|
textures/base/pack/server_incompatible.png
|
||||||
|
|
||||||
|
License of Minetest source code
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Irrlicht
|
||||||
|
---------------
|
||||||
|
|
||||||
|
This program uses IrrlichtMt, Minetest's fork of
|
||||||
|
the Irrlicht Engine. http://irrlicht.sourceforge.net/
|
||||||
|
|
||||||
|
The Irrlicht Engine License
|
||||||
|
|
||||||
|
Copyright © 2002-2005 Nikolaus Gebhardt
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute
|
||||||
|
it freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you
|
||||||
|
must not claim that you wrote the original software. If you use
|
||||||
|
this software in a product, an acknowledgment in the product
|
||||||
|
documentation would be appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must
|
||||||
|
not be misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
|
||||||
|
JThread
|
||||||
|
---------------
|
||||||
|
|
||||||
|
This program uses the JThread library. License for JThread follows:
|
||||||
|
|
||||||
|
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Lua
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Lua is licensed under the terms of the MIT license reproduced below.
|
||||||
|
This means that Lua is free software and can be used for both academic
|
||||||
|
and commercial purposes at absolutely no cost.
|
||||||
|
|
||||||
|
For details and rationale, see https://www.lua.org/license.html .
|
||||||
|
|
||||||
|
Copyright (C) 1994-2008 Lua.org, PUC-Rio.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Fonts
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bitstream Vera Fonts Copyright:
|
||||||
|
|
||||||
|
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
|
||||||
|
a trademark of Bitstream, Inc.
|
||||||
|
|
||||||
|
Arimo - Apache License, version 2.0
|
||||||
|
Digitized data copyright (c) 2010-2012 Google Corporation.
|
||||||
|
|
||||||
|
Cousine - Apache License, version 2.0
|
||||||
|
Digitized data copyright (c) 2010-2012 Google Corporation.
|
||||||
|
|
||||||
|
DroidSansFallBackFull:
|
||||||
|
|
||||||
|
Copyright (C) 2008 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
After Width: | Height: | Size: 225 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 95 B |
After Width: | Height: | Size: 142 B |
After Width: | Height: | Size: 68 B |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 147 B |
After Width: | Height: | Size: 182 B |
After Width: | Height: | Size: 201 B |
After Width: | Height: | Size: 210 B |
After Width: | Height: | Size: 173 B |
After Width: | Height: | Size: 191 B |
After Width: | Height: | Size: 873 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 288 B |
After Width: | Height: | Size: 436 B |
After Width: | Height: | Size: 766 B |
After Width: | Height: | Size: 708 B |
After Width: | Height: | Size: 255 B |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 908 B |
After Width: | Height: | Size: 971 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 144 B |
After Width: | Height: | Size: 255 B |
After Width: | Height: | Size: 68 B |
After Width: | Height: | Size: 234 B |
After Width: | Height: | Size: 331 B |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 580 B |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 124 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 420 B |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 714 B |
After Width: | Height: | Size: 586 B |
After Width: | Height: | Size: 281 B |
After Width: | Height: | Size: 178 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 449 B |
After Width: | Height: | Size: 142 B |
After Width: | Height: | Size: 140 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 763 B |
After Width: | Height: | Size: 714 B |
After Width: | Height: | Size: 413 B |
After Width: | Height: | Size: 354 B |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 227 B |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 916 B |
After Width: | Height: | Size: 273 B |
After Width: | Height: | Size: 713 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 385 B |
After Width: | Height: | Size: 251 B |
After Width: | Height: | Size: 244 B |
After Width: | Height: | Size: 245 B |
After Width: | Height: | Size: 213 B |
After Width: | Height: | Size: 492 B |
After Width: | Height: | Size: 202 B |
After Width: | Height: | Size: 912 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 292 B |
After Width: | Height: | Size: 193 B |
After Width: | Height: | Size: 254 B |
After Width: | Height: | Size: 126 B |
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,102 @@
|
||||||
|
-- Simple error diffusion dithering
|
||||||
|
-- TODO use HSV/HSL for dithering closer to human perception
|
||||||
|
|
||||||
|
local matrices = {
|
||||||
|
none = {
|
||||||
|
fraction = 0
|
||||||
|
},
|
||||||
|
floyd_steinberg = {
|
||||||
|
fraction = 1/16,
|
||||||
|
{x_off = 1, 7};
|
||||||
|
{x_off = -1, 3, 5, 1};
|
||||||
|
},
|
||||||
|
jarvis_judice_ninke = {
|
||||||
|
fraction = 1/48,
|
||||||
|
{x_off = 1, 7, 5};
|
||||||
|
{x_off = -2, 3, 5, 7, 5, 3};
|
||||||
|
{x_off = -2, 1, 3, 5, 3, 1};
|
||||||
|
},
|
||||||
|
stucke = {
|
||||||
|
fraction = 1/42,
|
||||||
|
{x_off = 1, 8, 4};
|
||||||
|
{x_off = -2, 2, 4, 8, 4, 2};
|
||||||
|
{x_off = -2, 1, 2, 4, 2, 1};
|
||||||
|
},
|
||||||
|
atkinson = {
|
||||||
|
fraction = 1/8,
|
||||||
|
{x_off = 1, 1, 1};
|
||||||
|
{x_off = -1, 1, 1, 1 };
|
||||||
|
{x_off = 0, 1 };
|
||||||
|
},
|
||||||
|
burkes = {
|
||||||
|
fraction = 1/42,
|
||||||
|
{x_off = 1, 8, 4};
|
||||||
|
{x_off = -2, 2, 4, 8, 4, 2};
|
||||||
|
},
|
||||||
|
sierra = {
|
||||||
|
fraction = 1/16,
|
||||||
|
{x_off = 1, 4, 3};
|
||||||
|
{x_off = -2, 1, 2, 3, 2, 1};
|
||||||
|
},
|
||||||
|
sierra_lite = {
|
||||||
|
fraction = 1/4,
|
||||||
|
{x_off = 1, 2},
|
||||||
|
{x_off = -1, 1, 1 },
|
||||||
|
},
|
||||||
|
two_row_sierra = {
|
||||||
|
fraction = 1/32,
|
||||||
|
{x_off = 1, 5, 3},
|
||||||
|
{x_off = -2, 2, 4, 5, 4, 2},
|
||||||
|
{x_off = -1, 2, 3, 2 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
texgen.dithering_methods = matrices
|
||||||
|
|
||||||
|
local clamp = modlib.math.clamp
|
||||||
|
return function(method)
|
||||||
|
local matrix = assert(matrices[method])
|
||||||
|
return function(png, closest_color)
|
||||||
|
local width = png.width
|
||||||
|
local diffused_errors = {}
|
||||||
|
local zero_err = {r = 0, g = 0, b = 0}
|
||||||
|
for index, color in ipairs(png.data) do
|
||||||
|
local x, y = (index - 1) % width, math.floor((index - 1) / width)
|
||||||
|
color = modlib.minetest.colorspec.from_number(color)
|
||||||
|
local diff_err = diffused_errors[index] or zero_err
|
||||||
|
diffused_errors[index] = nil
|
||||||
|
local col_err = {
|
||||||
|
r = clamp(color.r + diff_err.r, 0, 255),
|
||||||
|
g = clamp(color.g + diff_err.g, 0, 255),
|
||||||
|
b = clamp(color.b + diff_err.b, 0, 255)
|
||||||
|
}
|
||||||
|
local new_color = closest_color(col_err)
|
||||||
|
png.data[index] = modlib.minetest.colorspec.new{
|
||||||
|
r = new_color.r,
|
||||||
|
g = new_color.g,
|
||||||
|
b = new_color.b,
|
||||||
|
a = color.a -- keep alpha
|
||||||
|
}:to_number()
|
||||||
|
-- Diffuse error
|
||||||
|
local weight = matrix.fraction * color.a / 255
|
||||||
|
if weight > 0 then
|
||||||
|
local err = {
|
||||||
|
r = weight * (new_color.r - col_err.r),
|
||||||
|
g = weight * (new_color.g - col_err.g),
|
||||||
|
b = weight * (new_color.b - col_err.b)
|
||||||
|
}
|
||||||
|
for y_off, row in ipairs(matrix) do
|
||||||
|
local ey = y + y_off - 1
|
||||||
|
for x_off, factor in ipairs(row) do
|
||||||
|
local ex = x + row.x_off + x_off - 1
|
||||||
|
local idx = ey * width + ex
|
||||||
|
local derr = diffused_errors[idx] or {r = 0, g = 0, b = 0}
|
||||||
|
derr.r = derr.r + factor * err.r
|
||||||
|
derr.g = derr.g + factor * err.g
|
||||||
|
derr.b = derr.b + factor * err.b
|
||||||
|
diffused_errors[idx] = derr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,39 @@
|
||||||
|
local http = ...
|
||||||
|
local palettes = modlib.mod.get_resource"palettes"
|
||||||
|
minetest.register_chatcommand("download_palette", {
|
||||||
|
params = "<url>",
|
||||||
|
description = "Download a palette to use for generating textures",
|
||||||
|
privs = {server = true},
|
||||||
|
func = function(name, url)
|
||||||
|
local palette_name = url:match"/([A-Za-z0-9-_]+)%.png$"
|
||||||
|
if not (url:match"^https://" and palette_name) then
|
||||||
|
return false, "URL must be a valid HTTPS URL with a filename consisting of ASCII letters, digits, hyphens and underscores and ending in a .png extension." -- luacheck: ignore
|
||||||
|
end
|
||||||
|
http.fetch({
|
||||||
|
url = url,
|
||||||
|
method = "GET"
|
||||||
|
}, function(res)
|
||||||
|
local failure = (res.timeout and "Timeout") or (not res.succeeded and ("HTTP Status Code %d"):format(res.code))
|
||||||
|
if failure then
|
||||||
|
minetest.chat_send_player(name, ("Downloading from URL %s failed: %s"):format(url, failure))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local stream = modlib.text.inputstream(res.data)
|
||||||
|
local status, res_or_err = pcall(modlib.minetest.decode_png, stream)
|
||||||
|
if stream:read(1) then
|
||||||
|
status, res_or_err = false, "EOF expected"
|
||||||
|
end
|
||||||
|
if status then
|
||||||
|
-- Rewrite & store PNG
|
||||||
|
local png = res_or_err
|
||||||
|
modlib.minetest.convert_png_to_argb8(png)
|
||||||
|
modlib.table.shuffle(png.data) -- for copyright reasons
|
||||||
|
modlib.file.write(modlib.file.concat_path{palettes, palette_name .. ".png"},
|
||||||
|
modlib.minetest.encode_png(png.width, png.height, png.data))
|
||||||
|
minetest.chat_send_player(name, ("Palette %s added to palettes!"):format(palette_name))
|
||||||
|
else
|
||||||
|
minetest.chat_send_player(name, ("PNG image from URL %s is invalid: %s"):format(url, res_or_err))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
})
|
|
@ -0,0 +1,130 @@
|
||||||
|
local modname = "texgen"
|
||||||
|
local modpath = minetest.get_modpath(modname)
|
||||||
|
|
||||||
|
-- Rewrite mod.conf on startup
|
||||||
|
do
|
||||||
|
local conf = Settings(modpath .. "/mod.conf")
|
||||||
|
conf:set("name", modname)
|
||||||
|
conf:set("description", "Dynamically generated texture packs")
|
||||||
|
conf:set("depends", "modlib")
|
||||||
|
local to_depend = {}
|
||||||
|
local to_dep_list = {}
|
||||||
|
for _, modname in pairs(minetest.get_modnames()) do to_depend[modname] = true end -- luacheck: ignore
|
||||||
|
local opt_dep_str = conf:get"optional_depends" or ""
|
||||||
|
for opt_depend in opt_dep_str:gmatch"[^%s,]+" do
|
||||||
|
table.insert(to_dep_list, opt_depend)
|
||||||
|
to_depend[opt_depend] = nil
|
||||||
|
end
|
||||||
|
to_depend.texgen = nil -- no circular dependency
|
||||||
|
to_depend.modlib = nil -- modlib is already a hard dependency
|
||||||
|
if next(to_depend) or not modlib then
|
||||||
|
for dep in pairs(to_depend) do table.insert(to_dep_list, dep) end
|
||||||
|
conf:set("optional_depends", table.concat(to_dep_list, ", "))
|
||||||
|
conf:write()
|
||||||
|
error"mod.conf updated to optionally depend on all enabled mods, please restart the game"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(modlib.version >= 94, "update modlib to version rolling-94 or newer")
|
||||||
|
|
||||||
|
-- Register palette downloading command if HTTP API is available
|
||||||
|
local http = minetest.request_http_api()
|
||||||
|
if http then
|
||||||
|
assert(loadfile(modlib.mod.get_resource"download_palette.lua"))(http)
|
||||||
|
end
|
||||||
|
|
||||||
|
local texture_path = modpath .. "/textures/"
|
||||||
|
if minetest.rmdir then
|
||||||
|
-- Clear texture directory on startup
|
||||||
|
minetest.rmdir(texture_path, true)
|
||||||
|
end
|
||||||
|
minetest.mkdir(texture_path)
|
||||||
|
|
||||||
|
local function get_path(filename)
|
||||||
|
local path
|
||||||
|
local mod = modlib.minetest.media.mods[filename]
|
||||||
|
if mod == modname then -- media overridden by this mod
|
||||||
|
local overridden_paths = modlib.minetest.media.overridden_paths[filename]
|
||||||
|
if not overridden_paths then return end
|
||||||
|
path = overridden_paths[#overridden_paths]
|
||||||
|
else
|
||||||
|
path = modlib.minetest.media.paths[filename]
|
||||||
|
end
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
|
||||||
|
texgen = {} -- HACK only use the mod namespace to expose the dithering methods to the schema...
|
||||||
|
local dithering = modlib.mod.include"dithering.lua" -- sets texgen.dithering_methods
|
||||||
|
local conf = modlib.mod.configuration()
|
||||||
|
texgen = nil -- ...delete it afterwards
|
||||||
|
|
||||||
|
local palette, dither
|
||||||
|
if conf.palette.name then
|
||||||
|
palette = modlib.mod.include"palette.lua"(conf.palette.name)
|
||||||
|
dither = dithering(conf.palette.dithering)
|
||||||
|
end
|
||||||
|
local saturate, monochrome, invert = conf.saturate, conf.monochrome, conf.invert
|
||||||
|
local average
|
||||||
|
if conf.average then
|
||||||
|
average = modlib.mod.include"average_color.lua"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function read_convert_png(path)
|
||||||
|
local file = assert(io.open(path, "rb"))
|
||||||
|
local png = modlib.minetest.decode_png(file)
|
||||||
|
assert(not file:read(1), "EOF expected")
|
||||||
|
file:close()
|
||||||
|
modlib.minetest.convert_png_to_argb8(png)
|
||||||
|
return png
|
||||||
|
end
|
||||||
|
|
||||||
|
local clamp = modlib.math.clamp
|
||||||
|
local function transform_png(filename, path)
|
||||||
|
local png = read_convert_png(path)
|
||||||
|
if palette then
|
||||||
|
dither(png, palette)
|
||||||
|
end
|
||||||
|
modlib.table.map(png.data, function(color_num)
|
||||||
|
local color = modlib.minetest.colorspec.from_number(color_num)
|
||||||
|
if saturate ~= 1 then
|
||||||
|
-- See https://alienryderflex.com/saturation.html
|
||||||
|
local P = math.sqrt(0.299 * color.r^2 + 0.587 * color.g^2 + 0.114 * color.b^2)
|
||||||
|
local res = modlib.vector.apply({
|
||||||
|
r = clamp((color.r - P) * saturate + P + 0.5, 0, 255),
|
||||||
|
g = clamp((color.g - P) * saturate + P + 0.5, 0, 255),
|
||||||
|
b = clamp((color.b - P) * saturate + P + 0.5, 0, 255),
|
||||||
|
}, math.floor)
|
||||||
|
color.r, color.g, color.b = res.r, res.g, res.b
|
||||||
|
end
|
||||||
|
if invert then
|
||||||
|
color.r, color.g, color.b = 255 - color.r, 255 - color.g, 255 - color.b
|
||||||
|
end
|
||||||
|
if monochrome then
|
||||||
|
local brightness = math.floor(0.299 * color.r + 0.587 * color.g + 0.114 * color.b + 0.5)
|
||||||
|
color.r, color.g, color.b = brightness, brightness, brightness
|
||||||
|
end
|
||||||
|
return color:to_number()
|
||||||
|
end)
|
||||||
|
local width, height, data = png.width, png.height, png.data
|
||||||
|
if average then
|
||||||
|
width, height, data = 1, 1, {average(png):to_number()}
|
||||||
|
end
|
||||||
|
modlib.file.write(modlib.file.concat_path{modpath, "textures", filename},
|
||||||
|
modlib.minetest.encode_png(width, height, data))
|
||||||
|
end
|
||||||
|
|
||||||
|
for filename in pairs(modlib.minetest.media.paths) do
|
||||||
|
local _, ext = modlib.file.get_extension(filename)
|
||||||
|
if ext == "png" then
|
||||||
|
local path = get_path(filename)
|
||||||
|
-- May be (only) overridden media from this mod, which does not have a path (as it was deleted)
|
||||||
|
if path then transform_png(filename, path) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Builtin textures aren't provided by mods and are thus unknown to modlib; provide them through this mod
|
||||||
|
for _, filename in ipairs(minetest.get_dir_list(modlib.file.concat_path{modpath, "builtin"}, false)) do
|
||||||
|
-- Don't override builtin overrides by other mods
|
||||||
|
if filename:match"%.png$" and not (modlib.minetest.media[filename] and modlib.minetest.overridden_paths[filename]) then
|
||||||
|
transform_png(filename, modlib.file.concat_path{modpath, "builtin", filename})
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
name = texgen
|
||||||
|
title = Texture Generator
|
||||||
|
author = appguru(eu)
|
||||||
|
description = Dynamically generated texture packs
|
||||||
|
depends = modlib
|
|
@ -0,0 +1,42 @@
|
||||||
|
local function relative_color_distance(color, other_color)
|
||||||
|
-- See https://www.compuphase.com/cmetric.htm
|
||||||
|
local redmean = (color.r + other_color.r) / 2
|
||||||
|
-- Omits the square root as this only has to be relative
|
||||||
|
return (2 + redmean/256) * (color.r-other_color.r)^2
|
||||||
|
+ 4 * (color.g-other_color.g)^2
|
||||||
|
+ (2 + (255 - redmean)/256) * (color.b-other_color.b)^2
|
||||||
|
end
|
||||||
|
local palettes = modlib.mod.get_resource"palettes"
|
||||||
|
return function(name)
|
||||||
|
local path = modlib.file.concat_path{palettes, name .. ".png"}
|
||||||
|
local file = assert(io.open(path, "rb"))
|
||||||
|
local png = modlib.minetest.decode_png(file)
|
||||||
|
assert(not file:read(1), "EOF expected")
|
||||||
|
file:close()
|
||||||
|
modlib.minetest.convert_png_to_argb8(png)
|
||||||
|
local colors = {}
|
||||||
|
for _, color in pairs(png.data) do
|
||||||
|
-- TODO ignore colors with alpha 0?
|
||||||
|
local rgb = color % 0x1000000
|
||||||
|
colors[rgb] = true
|
||||||
|
end
|
||||||
|
local palette_colors = {}
|
||||||
|
for colornum in pairs(colors) do
|
||||||
|
table.insert(palette_colors, modlib.minetest.colorspec.from_number(colornum))
|
||||||
|
end
|
||||||
|
return function(color)
|
||||||
|
-- Find closest color using a linear search; a k-d-tree can't be employed here because the metric isn't euclidean
|
||||||
|
local closest_color = palette_colors[1]
|
||||||
|
local closest_distance = relative_color_distance(color, closest_color)
|
||||||
|
for i = 2, #palette_colors do
|
||||||
|
local palette_color = palette_colors[i]
|
||||||
|
local distance = relative_color_distance(color, palette_color)
|
||||||
|
-- TODO deal with same distances through random choice?
|
||||||
|
if distance < closest_distance then
|
||||||
|
closest_color = palette_color
|
||||||
|
closest_distance = distance
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return closest_color
|
||||||
|
end
|
||||||
|
end
|
After Width: | Height: | Size: 325 B |
After Width: | Height: | Size: 253 B |
After Width: | Height: | Size: 133 B |
After Width: | Height: | Size: 325 B |
After Width: | Height: | Size: 83 B |
After Width: | Height: | Size: 197 B |
|
@ -0,0 +1,48 @@
|
||||||
|
local palettes = {}
|
||||||
|
for _, filename in ipairs(minetest.get_dir_list(modlib.mod.get_resource"palettes", false)) do
|
||||||
|
local palette = filename:match"(.+)%.png$"
|
||||||
|
if palette then palettes[palette] = true end
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
type = "table",
|
||||||
|
entries = {
|
||||||
|
palette = {
|
||||||
|
type = "table",
|
||||||
|
entries = {
|
||||||
|
name = {
|
||||||
|
type = "string",
|
||||||
|
description = "Name of the palette to use (without extension)",
|
||||||
|
values = palettes,
|
||||||
|
default = "apollo"
|
||||||
|
},
|
||||||
|
dithering = {
|
||||||
|
type = "string",
|
||||||
|
description = "Dithering method to use",
|
||||||
|
values = texgen.dithering_methods,
|
||||||
|
default = "floyd_steinberg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saturate = {
|
||||||
|
type = "number",
|
||||||
|
description = "Increase or decrease saturation by a factor",
|
||||||
|
range = {min = 0.1, max = 10},
|
||||||
|
default = 1
|
||||||
|
},
|
||||||
|
monochrome = {
|
||||||
|
type = "boolean",
|
||||||
|
description = "Convert RGB to monochrome (greyscale)",
|
||||||
|
default = false
|
||||||
|
},
|
||||||
|
invert = {
|
||||||
|
type = "boolean",
|
||||||
|
description = "Invert the RGB colors",
|
||||||
|
default = false
|
||||||
|
},
|
||||||
|
average = {
|
||||||
|
type = "boolean",
|
||||||
|
description = "Replace each texture with a single pixel of its weighted average RGB color",
|
||||||
|
default = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 179 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
# Replace each texture with a single pixel of its weighted average RGB color
|
||||||
|
texgen.average (Texgen Average) bool false
|
||||||
|
|
||||||
|
# Invert the RGB colors
|
||||||
|
texgen.invert (Texgen Invert) bool false
|
||||||
|
|
||||||
|
# Convert RGB to monochrome (greyscale)
|
||||||
|
texgen.monochrome (Texgen Monochrome) bool false
|
||||||
|
|
||||||
|
# Increase or decrease saturation by a factor
|
||||||
|
texgen.saturate (Texgen Saturate) float 1 0.100000 10.000000
|
||||||
|
|
||||||
|
[texgen.palette]
|
||||||
|
|
||||||
|
# Dithering method to use
|
||||||
|
texgen.palette.dithering (Texgen Palette Dithering) enum floyd_steinberg sierra,stucke,sierra_lite,jarvis_judice_ninke,floyd_steinberg,none,two_row_sierra,burkes,atkinson
|
||||||
|
|
||||||
|
# Name of the palette to use (without extension)
|
||||||
|
texgen.palette.name (Texgen Palette Name) enum apollo soggy-newspapers,apollo,zughy-32,aap-64,pico-8,resurrect-64
|