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:
Rogier 2014-06-19 11:37:16 +02:00
parent e27d181d9f
commit b99111e038
6 changed files with 208 additions and 39 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -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`

View File

@ -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;

View File

@ -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;

View File

@ -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();