From b99111e03816cccb8908edaa4606c3dc06b853bf Mon Sep 17 00:00:00 2001 From: Rogier Date: Thu, 19 Jun 2014 11:37:16 +0200 Subject: [PATCH] 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. --- PixelAttributes.cpp | 146 ++++++++++++++++++++++++++++++++++++++++---- PixelAttributes.h | 26 +++++++- README.rst | 44 +++++++++---- TileGenerator.cpp | 6 +- TileGenerator.h | 3 +- mapper.cpp | 22 ++++--- 6 files changed, 208 insertions(+), 39 deletions(-) diff --git a/PixelAttributes.cpp b/PixelAttributes.cpp index a32bf4b..a0f08c5 100644 --- a/PixelAttributes.cpp +++ b/PixelAttributes.cpp @@ -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: +// = / +// = / n +// = / n +// = / n +// n = 0 +// The converse is (n would normally be set to 1): +// = * +// 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 } diff --git a/PixelAttributes.h b/PixelAttributes.h index e2812f8..f057a39 100644 --- a/PixelAttributes.h +++ b/PixelAttributes.h @@ -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) diff --git a/README.rst b/README.rst index b08e106..c32af7a 100644 --- a/README.rst +++ b/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` diff --git a/TileGenerator.cpp b/TileGenerator.cpp index 1b4a548..7940789 100644 --- a/TileGenerator.cpp +++ b/TileGenerator.cpp @@ -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; diff --git a/TileGenerator.h b/TileGenerator.h index d78fc0a..f203bda 100644 --- a/TileGenerator.h +++ b/TileGenerator.h @@ -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; diff --git a/mapper.cpp b/mapper.cpp index 8f21568..f945505 100644 --- a/mapper.cpp +++ b/mapper.cpp @@ -19,6 +19,7 @@ #include #include #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 \", color\"\n" " --draw[map]line \" 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();