315 lines
7.5 KiB
C++
315 lines
7.5 KiB
C++
/*
|
|
* =====================================================================
|
|
* Version: 1.0
|
|
* Created: 25.08.2012 10:55:27
|
|
* Author: Miroslav Bendík
|
|
* Company: LinuxOS.sk
|
|
* =====================================================================
|
|
*/
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include "PixelAttributes.h"
|
|
|
|
using namespace std;
|
|
|
|
PixelAttribute::AlphaMixingMode PixelAttribute::m_mixMode = PixelAttribute::AlphaMixCumulative;
|
|
|
|
PixelAttributes::PixelAttributes():
|
|
m_pixelAttributes(0)
|
|
{
|
|
}
|
|
|
|
PixelAttributes::~PixelAttributes()
|
|
{
|
|
freeAttributes();
|
|
}
|
|
|
|
void PixelAttributes::setParameters(int width, int lines, int nextY, int scale, bool defaultEmpty)
|
|
{
|
|
freeAttributes();
|
|
m_width = width + 1; // 1px gradient calculation
|
|
m_previousLine = 0;
|
|
m_firstLine = 1;
|
|
m_lastLine = m_firstLine + lines - 1;
|
|
m_emptyLine = m_lastLine + 1;
|
|
m_lineCount = m_emptyLine + 1;
|
|
m_firstY = 0;
|
|
m_nextY = nextY;
|
|
m_lastY = -1;
|
|
m_firstUnshadedY = 0;
|
|
m_scale = scale;
|
|
|
|
m_pixelAttributes = new PixelAttribute *[m_lineCount];
|
|
if (!m_pixelAttributes)
|
|
throw std::runtime_error("Failed to allocate memory for PixelAttributes");
|
|
|
|
for (int i = 0; i < m_lineCount; ++i) {
|
|
m_pixelAttributes[i] = new PixelAttribute[m_width];
|
|
if (!m_pixelAttributes[i])
|
|
throw std::runtime_error("Failed to allocate memory for PixelAttributes");
|
|
}
|
|
for (int i=0; i<m_lineCount; i++)
|
|
for (int j=0; j<m_width; j++) {
|
|
m_pixelAttributes[i][j].m_a=0;
|
|
if (defaultEmpty)
|
|
m_pixelAttributes[i][j].nextEmpty = (j - 1) % (16 / scale) == 0;
|
|
else
|
|
m_pixelAttributes[i][j].nextEmpty = false;
|
|
}
|
|
}
|
|
|
|
void PixelAttributes::scroll(int keepY)
|
|
{
|
|
int scroll = keepY - m_firstY;
|
|
if (scroll > 0) {
|
|
int i;
|
|
for (i = m_previousLine; i + scroll <= m_lastLine; i++) {
|
|
PixelAttribute *tmp;
|
|
tmp = m_pixelAttributes[i];
|
|
m_pixelAttributes[i] = m_pixelAttributes[i + scroll];
|
|
m_pixelAttributes[i + scroll] = tmp;
|
|
}
|
|
|
|
size_t lineLength = m_width * sizeof(PixelAttribute);
|
|
for (; i <= m_lastLine; ++i) {
|
|
memcpy(m_pixelAttributes[i], m_pixelAttributes[m_emptyLine], lineLength);
|
|
}
|
|
|
|
m_firstY += scroll;
|
|
m_nextY = m_firstY;
|
|
m_firstUnshadedY -= scroll;
|
|
if (m_firstUnshadedY < m_firstY) m_firstUnshadedY = m_firstY;
|
|
}
|
|
}
|
|
|
|
void PixelAttributes::freeAttributes()
|
|
{
|
|
if (m_pixelAttributes) {
|
|
for (int i = 0; i < m_lineCount; ++i) {
|
|
if (m_pixelAttributes[i] != 0) {
|
|
delete[] m_pixelAttributes[i];
|
|
}
|
|
}
|
|
delete[] m_pixelAttributes;
|
|
m_pixelAttributes = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static inline double colorSafeBounds(double color)
|
|
{
|
|
if (color > 1) {
|
|
return 1;
|
|
}
|
|
else if (color < 0) {
|
|
return 0;
|
|
}
|
|
else {
|
|
return color;
|
|
}
|
|
}
|
|
|
|
|
|
void PixelAttributes::renderShading(double emphasis, bool drawAlpha)
|
|
{
|
|
int y;
|
|
for (y = yCoord2Line(m_firstUnshadedY); y <= yCoord2Line(m_lastY); y++) {
|
|
for (int x = 1; x < m_width; x++) {
|
|
if (m_pixelAttributes[y][x].nextEmpty) {
|
|
x += 16 / m_scale - 1;
|
|
continue;
|
|
}
|
|
if (!m_pixelAttributes[y][x].isNormalized())
|
|
m_pixelAttributes[y][x].normalize();
|
|
if (!m_pixelAttributes[y][x].is_valid()) {
|
|
if (x + 1 < m_width && !m_pixelAttributes[y][x + 1].isNormalized())
|
|
m_pixelAttributes[y][x + 1].normalize();
|
|
x++;
|
|
continue;
|
|
}
|
|
if (!m_pixelAttributes[y - 1][x].is_valid() || !m_pixelAttributes[y][x - 1].is_valid())
|
|
continue;
|
|
if (!m_pixelAttributes[y][x].m_a)
|
|
continue;
|
|
double h = m_pixelAttributes[y][x].m_h;
|
|
double h1 = m_pixelAttributes[y][x - 1].m_a ? m_pixelAttributes[y][x - 1].m_h : h;
|
|
double h2 = m_pixelAttributes[y - 1][x].m_a ? m_pixelAttributes[y - 1][x].m_h : h;
|
|
double d = (h - h1) + (h - h2);
|
|
if (d > 3) {
|
|
d = 3;
|
|
}
|
|
d = d * 12 / 255 * emphasis;
|
|
#define pixel (m_pixelAttributes[y][x])
|
|
//PixelAttribute &pixel = m_pixelAttributes[y][x];
|
|
if (drawAlpha)
|
|
d = d * (1 - pixel.m_t);
|
|
pixel.m_r = colorSafeBounds(pixel.m_r + d);
|
|
pixel.m_g = colorSafeBounds(pixel.m_g + d);
|
|
pixel.m_b = colorSafeBounds(pixel.m_b + d);
|
|
#undef pixel
|
|
}
|
|
}
|
|
m_firstUnshadedY = y - yCoord2Line(0);
|
|
}
|
|
|
|
// 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 (!is_valid()) {
|
|
m_r = p.m_r;
|
|
m_g = p.m_g;
|
|
m_b = p.m_b;
|
|
m_a = p.m_a;
|
|
m_t = 0;
|
|
m_h = p.m_h;
|
|
m_n = p.m_n;
|
|
}
|
|
else 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) {
|
|
m_n = p.m_n;
|
|
m_r = p.m_r;
|
|
m_g = p.m_g;
|
|
m_b = p.m_b;
|
|
m_a = p.m_a;
|
|
m_t = 0;
|
|
}
|
|
m_h = p.m_h;
|
|
}
|
|
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 + 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;
|
|
else
|
|
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;
|
|
}
|
|
}
|
|
#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
|
|
}
|
|
|