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