Add a --drawalpha mode that keeps water (etc.) transparent to great depths
This new mode averages the colors of all transparent blocks, instead of making the colors progressively darker and more opaque. This 'average' mode is now the default when using --drawalpha. It can be explicitly selected using --drawalpha=average. The old modes can be selected using --drawalpha=cumulative[-darken]. It is recommended to change the colors of water as well. These are in a separate patch.
This commit is contained in:
parent
e27d181d9f
commit
b99111e038
@ -14,6 +14,8 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
PixelAttribute::AlphaMixingMode PixelAttribute::m_mixMode = PixelAttribute::AlphaMixCumulative;
|
||||
|
||||
PixelAttributes::PixelAttributes():
|
||||
m_pixelAttributes(0)
|
||||
{
|
||||
@ -113,7 +115,11 @@ void PixelAttributes::renderShading(bool drawAlpha)
|
||||
x += 15;
|
||||
continue;
|
||||
}
|
||||
if (!m_pixelAttributes[y][x].isNormalized())
|
||||
m_pixelAttributes[y][x].normalize();
|
||||
if (!m_pixelAttributes[y][x].is_valid() || !m_pixelAttributes[y - 1][x].is_valid()) {
|
||||
if (x + 1 < m_width && !m_pixelAttributes[y][x + 1].isNormalized())
|
||||
m_pixelAttributes[y][x + 1].normalize();
|
||||
x++;
|
||||
continue;
|
||||
}
|
||||
@ -142,7 +148,90 @@ void PixelAttributes::renderShading(bool drawAlpha)
|
||||
m_firstUnshadedY = y - yCoord2Line(0);
|
||||
}
|
||||
|
||||
void PixelAttribute::mixUnder(const PixelAttribute &p, bool darkenHighAlpha)
|
||||
// Meaning and usage of parameter 'n'.
|
||||
//
|
||||
// When n==0, all other values should be interpreted as
|
||||
// plain color / height / etc.
|
||||
//
|
||||
// When n is positive, some kind of pixel average is being
|
||||
// computed, and the other values represent a sum, with
|
||||
// n being the number of items summed.
|
||||
//
|
||||
// There is a twist when summing colors: Transparent colors
|
||||
// should not contribute the same amount to the final average
|
||||
// color as opaque colors do.
|
||||
// For that reason, the color values (r, g, b) are not simply
|
||||
// summed, but they are multiplied by their alpha values
|
||||
// first.
|
||||
//
|
||||
// Conversion from pixelattributes with n>0 to pixelattributes
|
||||
// with n==0 is performed as follows:
|
||||
// <color-value> = <sum-of-weighed-color-values> / <sum-of-alpha-values>
|
||||
// <alpha-value> = <sum-of-alpha-values> / n
|
||||
// <height> = <sum-of-heights> / n
|
||||
// <thick> = <sum-of-thick-values> / n
|
||||
// n = 0
|
||||
// The converse is (n would normally be set to 1):
|
||||
// <weighed-color-value> = <color-value> * <alpha-value>
|
||||
// n = 1
|
||||
// Color values with n>0 can be summed.
|
||||
|
||||
// normalize() converts from n>0 to n==0 representation
|
||||
void PixelAttribute::normalize(double count, Color defColor)
|
||||
{
|
||||
if (!m_n) {
|
||||
// Already normalized
|
||||
return;
|
||||
}
|
||||
if (m_n < count) {
|
||||
m_r += (defColor.r / 255.0) * (defColor.a / 255.0) * (count - m_n);
|
||||
m_g += (defColor.g / 255.0) * (defColor.a / 255.0) * (count - m_n);
|
||||
m_b += (defColor.b / 255.0) * (defColor.a / 255.0) * (count - m_n);
|
||||
m_a += (defColor.a / 255.0) * (count - m_n);
|
||||
m_h *= double(count) / m_n;
|
||||
m_t *= double(count) / m_n;
|
||||
m_n = count;
|
||||
}
|
||||
if (m_n != 1) {
|
||||
m_r /= m_a;
|
||||
m_g /= m_a;
|
||||
m_b /= m_a;
|
||||
m_a /= m_n;
|
||||
m_t /= m_n;
|
||||
m_h /= m_n;
|
||||
}
|
||||
m_n = 0;
|
||||
}
|
||||
|
||||
void PixelAttribute::add(const PixelAttribute &p)
|
||||
{
|
||||
if (!m_n) {
|
||||
m_r *= m_a;
|
||||
m_g *= m_a;
|
||||
m_b *= m_a;
|
||||
m_n = 1;
|
||||
}
|
||||
if (!p.m_n) {
|
||||
m_r += p.m_r * p.m_a;
|
||||
m_g += p.m_g * p.m_a;
|
||||
m_b += p.m_b * p.m_a;
|
||||
m_a += p.m_a;
|
||||
m_t += p.m_t;
|
||||
m_h += p.m_h;
|
||||
m_n++;
|
||||
}
|
||||
else {
|
||||
m_r += p.m_r;
|
||||
m_g += p.m_g;
|
||||
m_b += p.m_b;
|
||||
m_a += p.m_a;
|
||||
m_t += p.m_t;
|
||||
m_h += p.m_h;
|
||||
m_n += p.m_n;
|
||||
}
|
||||
}
|
||||
|
||||
void PixelAttribute::mixUnder(const PixelAttribute &p)
|
||||
{
|
||||
if (!is_valid() || m_a == 0) {
|
||||
if (!is_valid() || p.m_a != 0) {
|
||||
@ -155,26 +244,57 @@ void PixelAttribute::mixUnder(const PixelAttribute &p, bool darkenHighAlpha)
|
||||
}
|
||||
m_h = p.m_h;
|
||||
}
|
||||
else {
|
||||
else if ((m_mixMode & AlphaMixCumulative) == AlphaMixCumulative || (m_mixMode == AlphaMixAverage && p.m_a == 1)) {
|
||||
PixelAttribute pp(p);
|
||||
#ifdef DEBUG
|
||||
assert(pp.isNormalized());
|
||||
#else
|
||||
if (!pp.isNormalized())
|
||||
pp.normalize();
|
||||
#endif
|
||||
if (!isNormalized())
|
||||
normalize();
|
||||
int prev_alpha = alpha();
|
||||
m_r = (m_a * m_r + p.m_a * (1 - m_a) * p.m_r);
|
||||
m_g = (m_a * m_g + p.m_a * (1 - m_a) * p.m_g);
|
||||
m_b = (m_a * m_b + p.m_a * (1 - m_a) * p.m_b);
|
||||
m_a = (m_a + (1 - m_a) * p.m_a);
|
||||
if (p.m_a != 1)
|
||||
m_t = (m_t + p.m_t) / 2;
|
||||
m_h = p.m_h;
|
||||
if (prev_alpha >= 254 && p.alpha() < 255 && darkenHighAlpha) {
|
||||
m_r = (m_a * m_r + pp.m_a * (1 - m_a) * pp.m_r);
|
||||
m_g = (m_a * m_g + pp.m_a * (1 - m_a) * pp.m_g);
|
||||
m_b = (m_a * m_b + pp.m_a * (1 - m_a) * pp.m_b);
|
||||
m_a = (m_a + (1 - m_a) * pp.m_a);
|
||||
if (pp.m_a != 1)
|
||||
m_t = (m_t + pp.m_t) / 2;
|
||||
m_h = pp.m_h;
|
||||
if ((m_mixMode & AlphaMixDarkenBit) && prev_alpha >= 254 && pp.alpha() < 255) {
|
||||
// Darken
|
||||
// Parameters make deep water look good :-)
|
||||
// (maybe this setting should be per-node-type, and obtained from the colors file ?)
|
||||
m_r = m_r * 0.95;
|
||||
m_g = m_g * 0.95;
|
||||
m_b = m_b * 0.95;
|
||||
if (p.m_a != 1)
|
||||
m_t = (m_t + p.m_t) / 2;
|
||||
if (pp.m_a != 1)
|
||||
m_t = (m_t + pp.m_t) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
else if (m_mixMode == AlphaMixAverage && p.m_a != 1) {
|
||||
#else
|
||||
else {
|
||||
#endif
|
||||
if (p.m_a == 1)
|
||||
normalize();
|
||||
double h = p.m_h;
|
||||
double t = m_t;
|
||||
add(p);
|
||||
if (p.m_a == 1) {
|
||||
normalize();
|
||||
m_t = t;
|
||||
m_a = 1;
|
||||
}
|
||||
m_h = m_n * h;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else {
|
||||
// Internal error
|
||||
assert(1 && m_mixMode);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,15 @@
|
||||
|
||||
class PixelAttribute {
|
||||
public:
|
||||
enum AlphaMixingMode {
|
||||
AlphaMixDarkenBit = 0x01,
|
||||
AlphaMixCumulative = 0x02,
|
||||
AlphaMixCumulativeDarken = 0x03,
|
||||
AlphaMixAverage = 0x04,
|
||||
};
|
||||
static void setMixMode(AlphaMixingMode mode);
|
||||
PixelAttribute(): next16Empty(true), m_n(0), m_h(NAN), m_t(0), m_a(0), m_r(0), m_g(0), m_b(0) {};
|
||||
// PixelAttribute(const PixelAttribute &p);
|
||||
PixelAttribute(const Color &color, double height);
|
||||
PixelAttribute(const ColorEntry &entry, double height);
|
||||
bool next16Empty;
|
||||
@ -36,12 +44,16 @@ public:
|
||||
uint8_t alpha(void) const { return int(a() * 255 + 0.5); }
|
||||
uint8_t thicken(void) const { return int(t() * 255 + 0.5); }
|
||||
unsigned height(void) const { return unsigned(h() + 0.5); }
|
||||
bool isNormalized(void) const { return !m_n; }
|
||||
Color color(void) const { return Color(red(), green(), blue(), alpha()); }
|
||||
|
||||
inline bool is_valid() const { return !isnan(m_h); }
|
||||
PixelAttribute &operator=(const PixelAttribute &p);
|
||||
void mixUnder(const PixelAttribute &p, bool darkenHighAlpha);
|
||||
void normalize(double count = 0, Color defaultColor = Color(127, 127, 127));
|
||||
void add(const PixelAttribute &p);
|
||||
void mixUnder(const PixelAttribute &p);
|
||||
private:
|
||||
static AlphaMixingMode m_mixMode;
|
||||
double m_n;
|
||||
double m_h;
|
||||
double m_t;
|
||||
@ -94,6 +106,13 @@ inline void PixelAttributes::setLastY(int y)
|
||||
m_lastY = y;
|
||||
}
|
||||
|
||||
inline void PixelAttribute::setMixMode(AlphaMixingMode mode)
|
||||
{
|
||||
if (mode == AlphaMixDarkenBit)
|
||||
mode = AlphaMixCumulativeDarken;
|
||||
m_mixMode = mode;
|
||||
}
|
||||
|
||||
inline PixelAttribute &PixelAttributes::attribute(int y, int x)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
@ -106,6 +125,11 @@ inline PixelAttribute &PixelAttributes::attribute(int y, int x)
|
||||
return m_pixelAttributes[yCoord2Line(y)][x + 1];
|
||||
}
|
||||
|
||||
//inline PixelAttribute::PixelAttribute(const PixelAttribute &p) :
|
||||
//{
|
||||
// operator=(p);
|
||||
//}
|
||||
|
||||
inline PixelAttribute::PixelAttribute(const Color &color, double height) :
|
||||
next16Empty(false), m_n(0), m_h(height), m_t(0), m_a(color.a/255.0),
|
||||
m_r(color.r/255.0), m_g(color.g/255.0), m_b(color.b/255.0)
|
||||
|
44
README.rst
44
README.rst
@ -176,22 +176,42 @@ drawplayers:
|
||||
draworigin:
|
||||
Draw origin indicator, `--draworigin`
|
||||
|
||||
drawalpha[=darken|nodarken]:
|
||||
Allow blocks to be drawn with transparency, `--drawalpha=darken`
|
||||
drawalpha[=cumulative|cumulative-darken|average|none]:
|
||||
Allow blocks to be drawn with transparency, `--drawalpha=average`
|
||||
|
||||
Even with drawalpha, transparency decreases with depth. After a certain
|
||||
number of transparent blocks (e.g. water depth), the color will become opaque,
|
||||
and the underlying colors will no longer shine through. The height differences
|
||||
*will* still be visible though.
|
||||
In cumulative mode, transparency decreases with depth, and darkness of
|
||||
the color increases. After a certain number of transparent blocks
|
||||
(e.g. water depth), the color will become opaque, and the underlying
|
||||
colors will no longer shine through. The height differences *will*
|
||||
still be visible though.
|
||||
|
||||
With 'darken', after the color becomes opaque, it will gradually be darkened
|
||||
to visually simulate the bigger thickness of transparent blocks. The downside
|
||||
is that eventually, the color becomes black.
|
||||
In cumulative-darken mode, after the color becomes opaque, it will gradually
|
||||
be darkened to visually simulate the bigger thickness of transparent blocks.
|
||||
The downside is that eventually, the color becomes black.
|
||||
|
||||
Darken mode makes deeper, but not too deep water look much better. Very deep water
|
||||
will become black though.
|
||||
Cumulative-darken mode makes deeper, but not too deep water look much better.
|
||||
Very deep water will become black though.
|
||||
|
||||
With nodarken (default), after it becomes opaque, the color will not be darkened.
|
||||
In average mode, all transparent colors are averaged. The blocks remain transparent
|
||||
infinitely. If no parameter is given to --drawalpha, 'average' is the default.
|
||||
|
||||
'None' disables alpha drawing. This is the same as not using --drawalpha at all.
|
||||
|
||||
For backward compatibility, 'nodarken' is still recognised as alias for 'cumulative';
|
||||
'darken' is still recognised as alias for 'cumulative-darken'. Please don't use
|
||||
them, they may disappear in the future.
|
||||
|
||||
Note that each of the different modes has a different color definition
|
||||
for transparent blocks that looks best. For instance, for water, the following
|
||||
are suggested:
|
||||
|
||||
(disabled): 39 66 106 [192 224 - optional: alpha configuration will be ignored]
|
||||
|
||||
cumulative: 78 132 255 64 224
|
||||
|
||||
cumulative-darken: 78 132 255 64 224
|
||||
|
||||
average: 49 82 132 192 224 (look also good with alpha disabled)
|
||||
|
||||
drawair:
|
||||
Draw air blocks. `--drawair`
|
||||
|
@ -134,7 +134,6 @@ TileGenerator::TileGenerator():
|
||||
m_drawPlayers(false),
|
||||
m_drawScale(false),
|
||||
m_drawAlpha(false),
|
||||
m_darkenHighAlpha(false),
|
||||
m_drawAir(false),
|
||||
m_shading(true),
|
||||
m_border(0),
|
||||
@ -268,10 +267,9 @@ void TileGenerator::setDrawScale(bool drawScale)
|
||||
}
|
||||
}
|
||||
|
||||
void TileGenerator::setDrawAlpha(bool drawAlpha, bool darkenHighAlpha)
|
||||
void TileGenerator::setDrawAlpha(bool drawAlpha)
|
||||
{
|
||||
m_drawAlpha = drawAlpha;
|
||||
m_darkenHighAlpha = darkenHighAlpha;
|
||||
}
|
||||
|
||||
void TileGenerator::setDrawAir(bool drawAir)
|
||||
@ -1237,7 +1235,7 @@ inline void TileGenerator::renderMapBlock(const ustring &mapBlock, const BlockPo
|
||||
rowIsEmpty = false;
|
||||
#define nodeColor (*m_nodeIDColor[content])
|
||||
//const ColorEntry &nodeColor = *m_nodeIDColor[content];
|
||||
pixel.mixUnder(PixelAttribute(nodeColor, pos.y * 16 + y), m_darkenHighAlpha);
|
||||
pixel.mixUnder(PixelAttribute(nodeColor, pos.y * 16 + y));
|
||||
if ((m_drawAlpha && nodeColor.a == 0xff) || (!m_drawAlpha && nodeColor.a != 0)) {
|
||||
m_readedPixels[z] |= (1 << x);
|
||||
break;
|
||||
|
@ -91,7 +91,7 @@ public:
|
||||
void setDrawOrigin(bool drawOrigin);
|
||||
void setDrawPlayers(bool drawPlayers);
|
||||
void setDrawScale(bool drawScale);
|
||||
void setDrawAlpha(bool drawAlpha, bool darkenHighAlpha = false);
|
||||
void setDrawAlpha(bool drawAlpha);
|
||||
void setDrawAir(bool drawAir);
|
||||
void drawObject(const DrawObject &object) { m_drawObjects.push_back(object); }
|
||||
void setShading(bool shading);
|
||||
@ -169,7 +169,6 @@ private:
|
||||
bool m_drawPlayers;
|
||||
bool m_drawScale;
|
||||
bool m_drawAlpha;
|
||||
bool m_darkenHighAlpha;
|
||||
bool m_drawAir;
|
||||
bool m_shading;
|
||||
int m_border;
|
||||
|
22
mapper.cpp
22
mapper.cpp
@ -19,6 +19,7 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include "TileGenerator.h"
|
||||
#include "PixelAttributes.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@ -71,7 +72,7 @@ void usage()
|
||||
" --drawscale\n"
|
||||
" --drawplayers\n"
|
||||
" --draworigin\n"
|
||||
" --drawalpha[=[no]darken]\n"
|
||||
" --drawalpha[=cumulative|cumulative-darken|average|none]\n"
|
||||
" --drawair\n"
|
||||
" --draw[map]point \"<x>,<y> color\"\n"
|
||||
" --draw[map]line \"<geometry> color\"\n"
|
||||
@ -648,12 +649,19 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
if (optarg && string(optarg) == "darken")
|
||||
generator.setDrawAlpha(true, true);
|
||||
else if (optarg && string(optarg) == "nodarken")
|
||||
generator.setDrawAlpha(true, false);
|
||||
else if (!optarg)
|
||||
generator.setDrawAlpha(true);
|
||||
generator.setDrawAlpha(true);
|
||||
if (!optarg || !*optarg)
|
||||
PixelAttribute::setMixMode(PixelAttribute::AlphaMixAverage);
|
||||
else if (string(optarg) == "cumulative" || string(optarg) == "nodarken")
|
||||
// "nodarken" is supported for backwards compatibility
|
||||
PixelAttribute::setMixMode(PixelAttribute::AlphaMixCumulative);
|
||||
else if (string(optarg) == "darken" || string(optarg) == "cumulative-darken")
|
||||
// "darken" is supported for backwards compatibility
|
||||
PixelAttribute::setMixMode(PixelAttribute::AlphaMixCumulativeDarken);
|
||||
else if (string(optarg) == "average")
|
||||
PixelAttribute::setMixMode(PixelAttribute::AlphaMixAverage);
|
||||
else if (string(optarg) == "none")
|
||||
generator.setDrawAlpha(false);
|
||||
else {
|
||||
std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl;
|
||||
usage();
|
||||
|
Loading…
x
Reference in New Issue
Block a user