Switch to PNG encoding, send only the changed portion of the image for deltas.

This commit is contained in:
Andrzej Kapolka 2014-08-11 14:29:53 -07:00
parent 18256a657e
commit 8804fa3710
3 changed files with 268 additions and 53 deletions

View file

@ -490,21 +490,52 @@ HeightfieldData::HeightfieldData(const QByteArray& contents) :
}
const int BYTES_PER_PIXEL = 3;
const int ZERO_OFFSET = 128;
HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) :
_encoded(in.readAligned(bytes)) {
HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) {
read(in, bytes, color);
}
HeightfieldData::HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color) {
if (!reference) {
read(in, bytes, color);
return;
}
QMutexLocker locker(&reference->_encodedDeltaMutex);
reference->_encodedDelta = in.readAligned(bytes);
reference->_deltaData = this;
_contents = reference->_contents;
QImage image = QImage::fromData(_encoded).convertToFormat(QImage::Format_RGB888);
QBuffer buffer(&reference->_encodedDelta);
buffer.open(QIODevice::ReadOnly);
QImage image;
image.load(&buffer, "PNG");
QPoint offset = image.offset();
image = image.convertToFormat(QImage::Format_RGB888);
if (offset.x() == 0) {
set(image, color);
return;
}
int minX = offset.x() - 1;
int minY = offset.y() - 1;
if (color) {
_contents.resize(image.width() * image.height() * BYTES_PER_PIXEL);
memcpy(_contents.data(), image.constBits(), _contents.size());
int size = glm::sqrt(_contents.size() / (float)BYTES_PER_PIXEL);
char* dest = _contents.data() + (minY * size + minX) * BYTES_PER_PIXEL;
int destStride = size * BYTES_PER_PIXEL;
int srcStride = image.width() * BYTES_PER_PIXEL;
for (int y = 0; y < image.height(); y++) {
memcpy(dest, image.constScanLine(y), srcStride);
dest += destStride;
}
} else {
_contents.resize(image.width() * image.height());
char* dest = _contents.data();
for (const uchar* src = image.constBits(), *end = src + _contents.size() * BYTES_PER_PIXEL;
src != end; src += BYTES_PER_PIXEL) {
*dest++ = *src;
int size = glm::sqrt((float)_contents.size());
char* lineDest = _contents.data() + minY * size + minX;
for (int y = 0; y < image.height(); y++) {
const uchar* src = image.constScanLine(y);
for (char* dest = lineDest, *end = dest + image.width(); dest != end; dest++, src += BYTES_PER_PIXEL) {
*dest = *src;
}
lineDest += size;
}
}
}
@ -528,36 +559,168 @@ void HeightfieldData::write(Bitstream& out, bool color) {
}
QBuffer buffer(&_encoded);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "JPG");
image.save(&buffer, "PNG");
}
out << _encoded.size();
out.writeAligned(_encoded);
}
void HeightfieldData::writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color) {
if (!reference || reference->getContents().size() != _contents.size()) {
write(out, color);
return;
}
QMutexLocker locker(&reference->_encodedDeltaMutex);
if (reference->_encodedDelta.isEmpty() || reference->_deltaData != this) {
QImage image;
int minX, minY;
if (color) {
int size = glm::sqrt(_contents.size() / (float)BYTES_PER_PIXEL);
minX = size;
minY = size;
int maxX = -1, maxY = -1;
const char* src = _contents.constData();
const char* ref = reference->_contents.constData();
for (int y = 0; y < size; y++) {
bool difference = false;
for (int x = 0; x < size; x++, src += BYTES_PER_PIXEL, ref += BYTES_PER_PIXEL) {
if (src[0] != ref[0] || src[1] != ref[1] || src[2] != ref[2]) {
minX = qMin(minX, x);
maxX = qMax(maxX, x);
difference = true;
}
}
if (difference) {
minY = qMin(minY, y);
maxY = qMax(maxY, y);
}
}
int width = qMax(maxX - minX + 1, 0);
int height = qMax(maxY - minY + 1, 0);
image = QImage(width, height, QImage::Format_RGB888);
src = _contents.constData() + (minY * size + minX) * BYTES_PER_PIXEL;
int srcStride = size * BYTES_PER_PIXEL;
int destStride = width * BYTES_PER_PIXEL;
for (int y = 0; y < height; y++) {
memcpy(image.scanLine(y), src, destStride);
src += srcStride;
}
} else {
int size = glm::sqrt((float)_contents.size());
minX = size;
minY = size;
int maxX = -1, maxY = -1;
const char* src = _contents.constData();
const char* ref = reference->_contents.constData();
for (int y = 0; y < size; y++) {
bool difference = false;
for (int x = 0; x < size; x++) {
if (*src++ != *ref++) {
minX = qMin(minX, x);
maxX = qMax(maxX, x);
difference = true;
}
}
if (difference) {
minY = qMin(minY, y);
maxY = qMax(maxY, y);
}
}
int width = qMax(maxX - minX + 1, 0);
int height = qMax(maxY - minY + 1, 0);
image = QImage(width, height, QImage::Format_RGB888);
const uchar* lineSrc = (const uchar*)_contents.constData() + minY * size + minX;
for (int y = 0; y < height; y++) {
uchar* dest = image.scanLine(y);
for (const uchar* src = lineSrc, *end = src + width; src != end; src++) {
*dest++ = *src;
*dest++ = *src;
*dest++ = *src;
}
lineSrc += size;
}
}
QBuffer buffer(&reference->_encodedDelta);
buffer.open(QIODevice::WriteOnly);
image.setOffset(QPoint(minX + 1, minY + 1));
image.save(&buffer, "PNG");
reference->_deltaData = this;
}
out << reference->_encodedDelta.size();
out.writeAligned(reference->_encodedDelta);
}
void HeightfieldData::read(Bitstream& in, int bytes, bool color) {
set(QImage::fromData(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888), color);
}
void HeightfieldData::set(const QImage& image, bool color) {
if (color) {
_contents.resize(image.width() * image.height() * BYTES_PER_PIXEL);
memcpy(_contents.data(), image.constBits(), _contents.size());
} else {
_contents.resize(image.width() * image.height());
char* dest = _contents.data();
for (const uchar* src = image.constBits(), *end = src + _contents.size() * BYTES_PER_PIXEL;
src != end; src += BYTES_PER_PIXEL) {
*dest++ = *src;
}
}
}
HeightfieldAttribute::HeightfieldAttribute(const QString& name) :
InlineAttribute<HeightfieldDataPointer>(name) {
}
void HeightfieldAttribute::read(Bitstream& in, void*& value, bool isLeaf) const {
if (isLeaf) {
int size;
in >> size;
if (size == 0) {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer();
} else {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, false));
}
if (!isLeaf) {
return;
}
int size;
in >> size;
if (size == 0) {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer();
} else {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, false));
}
}
void HeightfieldAttribute::write(Bitstream& out, void* value, bool isLeaf) const {
if (isLeaf) {
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
if (data) {
data->write(out, false);
} else {
out << 0;
}
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
if (data) {
data->write(out, false);
} else {
out << 0;
}
}
void HeightfieldAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const {
if (!isLeaf) {
return;
}
int size;
in >> size;
if (size == 0) {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer();
} else {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(
in, size, decodeInline<HeightfieldDataPointer>(reference), false));
}
}
void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const {
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
if (data) {
data->writeDelta(out, decodeInline<HeightfieldDataPointer>(reference), false);
} else {
out << 0;
}
}
@ -635,25 +798,53 @@ HeightfieldColorAttribute::HeightfieldColorAttribute(const QString& name) :
}
void HeightfieldColorAttribute::read(Bitstream& in, void*& value, bool isLeaf) const {
if (isLeaf) {
int size;
in >> size;
if (size == 0) {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer();
} else {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, true));
}
if (!isLeaf) {
return;
}
int size;
in >> size;
if (size == 0) {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer();
} else {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, true));
}
}
void HeightfieldColorAttribute::write(Bitstream& out, void* value, bool isLeaf) const {
if (isLeaf) {
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
if (data) {
data->write(out, true);
} else {
out << 0;
}
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
if (data) {
data->write(out, true);
} else {
out << 0;
}
}
void HeightfieldColorAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const {
if (!isLeaf) {
return;
}
int size;
in >> size;
if (size == 0) {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer();
} else {
*(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(
in, size, decodeInline<HeightfieldDataPointer>(reference), true));
}
}
void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const {
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
if (data) {
data->writeDelta(out, decodeInline<HeightfieldDataPointer>(reference), true);
} else {
out << 0;
}
}

View file

@ -28,6 +28,7 @@ class QScriptEngine;
class QScriptValue;
class Attribute;
class HeightfieldData;
class MetavoxelData;
class MetavoxelLOD;
class MetavoxelNode;
@ -421,26 +422,35 @@ public:
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
};
typedef QExplicitlySharedDataPointer<HeightfieldData> HeightfieldDataPointer;
/// Contains a block of heightfield data.
class HeightfieldData : public QSharedData {
public:
HeightfieldData(const QByteArray& contents);
HeightfieldData(Bitstream& in, int bytes, bool color);
HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color);
const QByteArray& getContents() const { return _contents; }
void write(Bitstream& out, bool color);
void writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color);
private:
void read(Bitstream& in, int bytes, bool color);
void set(const QImage& image, bool color);
QByteArray _contents;
QByteArray _encoded;
QMutex _encodedMutex;
HeightfieldDataPointer _deltaData;
QByteArray _encodedDelta;
QMutex _encodedDeltaMutex;
};
typedef QExplicitlySharedDataPointer<HeightfieldData> HeightfieldDataPointer;
/// An attribute that stores heightfield data.
class HeightfieldAttribute : public InlineAttribute<HeightfieldDataPointer> {
Q_OBJECT
@ -451,7 +461,10 @@ public:
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const;
virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const;
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
};
@ -466,6 +479,9 @@ public:
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const;
virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const;
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
};

View file

@ -378,6 +378,7 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) {
float squaredRadiusReciprocal = 1.0f / squaredRadius;
const int EIGHT_BIT_MAXIMUM = 255;
float scaledHeight = _edit.height * EIGHT_BIT_MAXIMUM / info.size;
bool changed = false;
for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) {
uchar* dest = lineDest;
for (float x = startX; x <= endX; x += 1.0f, dest++) {
@ -386,14 +387,18 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) {
if (distanceSquared <= squaredRadius) {
// height falls off towards edges
int value = *dest + scaledHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal;
*dest = qMin(qMax(value, 0), EIGHT_BIT_MAXIMUM);
if (value != *dest) {
*dest = qMin(qMax(value, 0), EIGHT_BIT_MAXIMUM);
changed = true;
}
}
}
lineDest += size;
}
HeightfieldDataPointer newPointer(new HeightfieldData(contents));
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldDataPointer>(newPointer));
if (changed) {
HeightfieldDataPointer newPointer(new HeightfieldData(contents));
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldDataPointer>(newPointer));
}
return STOP_RECURSION;
}
@ -461,6 +466,7 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) {
char* lineDest = contents.data() + (int)z * stride + (int)startX * BYTES_PER_PIXEL;
float squaredRadius = scaledRadius * scaledRadius;
char red = _edit.color.red(), green = _edit.color.green(), blue = _edit.color.blue();
bool changed = false;
for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) {
char* dest = lineDest;
for (float x = startX; x <= endX; x += 1.0f, dest += BYTES_PER_PIXEL) {
@ -469,13 +475,15 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) {
dest[0] = red;
dest[1] = green;
dest[2] = blue;
changed = true;
}
}
lineDest += stride;
}
HeightfieldDataPointer newPointer(new HeightfieldData(contents));
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldDataPointer>(newPointer));
if (changed) {
HeightfieldDataPointer newPointer(new HeightfieldData(contents));
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldDataPointer>(newPointer));
}
return STOP_RECURSION;
}