diff --git a/kit/Delta.hpp b/kit/Delta.hpp index 95f6a1f18..82f871bbf 100644 --- a/kit/Delta.hpp +++ b/kit/Delta.hpp @@ -21,106 +21,149 @@ /// A quick and dirty delta generator for last tile changes class DeltaGenerator { + struct DeltaBitmapRow { + uint64_t _crc; + std::vector _pixels; + + bool identical(const DeltaBitmapRow &other) const + { + if (_crc != other._crc) + return false; + return _pixels == other._pixels; + } + }; + struct DeltaData { TileWireId _wid; - std::shared_ptr> _rawData; + int _width; + int _height; + std::vector _rows; }; - std::vector _deltaEntries; + std::vector> _deltaEntries; bool makeDelta( - const DeltaData &prevData, - const DeltaData &curData, + const DeltaData &prev, + const DeltaData &cur, std::vector& output) { - std::vector &prev = *prevData._rawData.get(); - std::vector &cur = *curData._rawData.get(); - - // FIXME: should we split and compress alpha separately ? - - if (prev.size() != cur.size()) + // TODO: should we split and compress alpha separately ? + if (prev._width != cur._width || prev._height != cur._height) { - LOG_ERR("mis-sized delta: " << prev.size() << " vs " << cur.size() << "bytes"); + LOG_ERR("mis-sized delta: " << prev._width << "x" << prev._height << " vs " + << cur._width << "x" << cur._height); return false; } output.push_back('D'); - LOG_TRC("building delta of " << prev.size() << "bytes"); - // FIXME: really lame - some RLE might help etc. - for (size_t i = 0; i < prev.size();) + LOG_TRC("building delta of a " << cur._width << "x" << cur._height << " bitmap"); + + // row move/copy src/dest is a byte. + assert (prev._height <= 256); + // column position is a byte. + assert (prev._width <= 256); + + // How do the rows look against each other ? + size_t lastMatchOffset = 0; + for (int y = 0; y < prev._height; ++y) { - int sameCount = 0; - while (i + sameCount < prev.size() && - prev[i+sameCount] == cur[i+sameCount]) + // Life is good where rows match: + if (prev._rows[y].identical(cur._rows[y])) + continue; + + // Hunt for other rows + bool matched = false; + for (int yn = 0; yn < prev._height && !matched; ++yn) { - ++sameCount; - } - if (sameCount > 0) - { -#if 0 - if (sameCount < 64) - output.push_back(sameCount); - else -#endif + size_t match = (y + lastMatchOffset + yn) % prev._height; + if (prev._rows[match].identical(cur._rows[y])) { - output.push_back(0x80 | 0x00); // long-same - output.push_back(sameCount & 0xff); - output.push_back(sameCount >> 8); + // TODO: if offsets are >256 - use 16bits? + + // hopefully find blocks of this. + lastMatchOffset = match - y; + output.push_back('c'); // copy-row + output.push_back(match); // src + output.push_back(y); // dest + matched = true; + continue; } - i += sameCount; - LOG_TRC("identical " << sameCount << "pixels"); } + if (matched) + continue; - int diffCount = 0; - while (i + diffCount < prev.size() && - (prev[i+diffCount] != cur[i+diffCount])) + // Our row is just that different: + const DeltaBitmapRow &curRow = cur._rows[y]; + const DeltaBitmapRow &prevRow = prev._rows[y]; + for (int x = 0; x < prev._width;) { - ++diffCount; - } + int same; + for (same = 0; same + x < prev._width && + prevRow._pixels[x+same] == curRow._pixels[x+same];) + ++same; - if (diffCount > 0) - { -#if 0 - if (diffCount < 64) - output.push_back(0x40 & diffCount); - else -#endif + x += same; + + int diff; + for (diff = 0; diff + x < prev._width && + (prevRow._pixels[x+diff] == curRow._pixels[x+diff] || diff < 2) && + diff < 254;) + ++diff; + if (diff > 0) { - output.push_back(0x80 | 0x40); // long-diff - output.push_back(diffCount & 0xff); - output.push_back(diffCount >> 8); - } + output.push_back('d'); + output.push_back(y); + output.push_back(x); + output.push_back(diff); - size_t dest = output.size(); - output.resize(dest + diffCount * 4); - memcpy(&output[dest], &cur[i], diffCount * 4); - LOG_TRC("different " << diffCount << "pixels"); - i += diffCount; + size_t dest = output.size(); + output.resize(dest + diff * 4); + memcpy(&output[dest], &curRow._pixels[x], diff * 4); + + LOG_TRC("different " << diff << "pixels"); + x += diff; + } } } + return true; } - std::shared_ptr> dataToVector( + std::shared_ptr dataToDeltaData( + TileWireId wid, unsigned char* pixmap, size_t startX, size_t startY, int width, int height, int bufferWidth, int bufferHeight) { + auto data = std::make_shared(); + data->_wid = wid; + assert (startX + width <= (size_t)bufferWidth); assert (startY + height <= (size_t)bufferHeight); - auto vector = std::make_shared>(); - LOG_TRC("Converting data to vector of size " + LOG_TRC("Converting pixel data to delta data of size " << (width * height * 4) << " width " << width << " height " << height); - vector->resize(width * height); + data->_width = width; + data->_height = height; + data->_rows.resize(height); for (int y = 0; y < height; ++y) { + DeltaBitmapRow &row = data->_rows[y]; size_t position = ((startY + y) * bufferWidth * 4) + (startX * 4); - memcpy(&(*vector)[y * width], pixmap + position, width * 4); + int32_t *src = reinterpret_cast(pixmap + position); + + // We get the hash ~for free as we copy - with a cheap hash. + uint64_t crc = 0x7fffffff - 1; + row._pixels.resize(width); + for (int x = 0; x < width; ++x) + { + crc = (crc << 7) + crc + src[x]; + row._pixels[x] = src[x]; + } } - return vector; + return data; } public: @@ -143,17 +186,15 @@ class DeltaGenerator { if (_deltaEntries.size() > 6) // FIXME: hard-coded ... _deltaEntries.erase(_deltaEntries.begin()); - // FIXME: assuming width etc. are all constant & so on. - DeltaData update; - update._wid = wid; - update._rawData = dataToVector(pixmap, startX, startY, width, height, - bufferWidth, bufferHeight); + std::shared_ptr update = + dataToDeltaData(wid, pixmap, startX, startY, width, height, + bufferWidth, bufferHeight); _deltaEntries.push_back(update); for (auto &old : _deltaEntries) { - if (oldWid == old._wid) - return makeDelta(old, update, output); + if (oldWid == old->_wid) + return makeDelta(*old, *update, output); } return false; } diff --git a/test/DeltaTests.cpp b/test/DeltaTests.cpp index 3532931e1..705b7d3ea 100644 --- a/test/DeltaTests.cpp +++ b/test/DeltaTests.cpp @@ -57,61 +57,85 @@ class DeltaTests : public CPPUNIT_NS::TestFixture const std::vector &delta); void assertEqual(const std::vector &a, - const std::vector &b); + const std::vector &b, + int width, int height); }; // Quick hack for debugging std::vector DeltaTests::applyDelta( const std::vector &pixmap, - png_uint_32 /* width */, png_uint_32 /* height */, + png_uint_32 width, png_uint_32 height, const std::vector &delta) { CPPUNIT_ASSERT(delta.size() >= 4); CPPUNIT_ASSERT(delta[0] == 'D'); -// std::cout << "apply delta of size " << delta.size() << "\n"; + std::cout << "apply delta of size " << delta.size() << "\n"; + // start with the same state. std::vector output = pixmap; - CPPUNIT_ASSERT_EQUAL(output.size(), pixmap.size()); - size_t offset = 0; - for (size_t i = 1; i < delta.size() && - offset < output.size();) + CPPUNIT_ASSERT_EQUAL(output.size(), size_t(pixmap.size())); + CPPUNIT_ASSERT_EQUAL(output.size(), size_t(width * height * 4)); + + size_t offset = 0, i; + for (i = 1; i < delta.size() && offset < output.size();) { - bool isChangedRun = delta[i++] & 64; - CPPUNIT_ASSERT(i < delta.size()); - uint32_t span = (unsigned char)delta[i++]; - CPPUNIT_ASSERT(i < delta.size()); - span += ((unsigned char)delta[i++])*256; - CPPUNIT_ASSERT(i < delta.size() || - (i == delta.size() && !isChangedRun)); - span *= 4; -// std::cout << "span " << span << " offset " << offset << "\n"; - if (isChangedRun) + switch (delta[i]) { - CPPUNIT_ASSERT(offset + span <= output.size()); - memcpy(&output[offset], &delta[i], span); - i += span; + case 'c': // copy row. + { + int srcRow = (uint8_t)(delta[i+1]); + int destRow = (uint8_t)(delta[i+2]); + + std::cout << "copy row " << srcRow << " to " << destRow << "\n"; + const char *src = &pixmap[width * srcRow * 4]; + char *dest = &output[width * destRow * 4]; + for (size_t j = 0; j < width * 4; ++j) + *dest++ = *src++; + i += 3; + break; + } + case 'd': // new run + { + int destRow = (uint8_t)(delta[i+1]); + int destCol = (uint8_t)(delta[i+2]); + size_t length = (uint8_t)(delta[i+3]); + i += 4; + + std::cout << "new " << length << " at " << destCol << ", " << destRow << "\n"; + CPPUNIT_ASSERT(length <= width - destCol); + + char *dest = &output[width * destRow * 4 + destCol * 4]; + for (size_t j = 0; j < length * 4 && i < delta.size(); ++j) + *dest++ = delta[i++]; + break; + } + default: + std::cout << "Unknown delta code " << delta[i] << "\n"; + CPPUNIT_ASSERT(false); + break; } - offset += span; } - CPPUNIT_ASSERT_EQUAL(pixmap.size(), output.size()); - CPPUNIT_ASSERT_EQUAL(output.size(), offset); + CPPUNIT_ASSERT_EQUAL(delta.size(), i); return output; } void DeltaTests::assertEqual(const std::vector &a, - const std::vector &b) + const std::vector &b, + int width, int /* height */) { CPPUNIT_ASSERT_EQUAL(a.size(), b.size()); for (size_t i = 0; i < a.size(); ++i) { if (a[i] != b[i]) { - std::cout << "Differences starting at byte " << i; + std::cout << "Differences starting at byte " << i << " " + << (i/4 % width) << ", " << (i / (width * 4)) << ":\n"; size_t len; - for (len = 0; a[i+len] != b[i+len] && i + len < a.size(); ++len) + for (len = 0; (a[i+len] != b[i+len] || len < 8) && i + len < a.size(); ++len) { - std::cout << std::hex << (int)((unsigned char)a[i+len]) << " "; + std::cout << std::hex << (int)((unsigned char)a[i+len]) << " != "; + std::cout << std::hex << (int)((unsigned char)b[i+len]) << " "; if (len > 0 && (len % 16 == 0)) std::cout<< "\n"; } @@ -157,7 +181,7 @@ void DeltaTests::testDeltaSequence() // Apply it to move to the second frame std::vector reText2 = applyDelta(text, width, height, delta); - assertEqual(reText2, text2); + assertEqual(reText2, text2, width, height); // Build a delta between text & text2Wid std::vector two2one; @@ -169,7 +193,7 @@ void DeltaTests::testDeltaSequence() // Apply it to get back to where we started std::vector reText = applyDelta(text2, width, height, two2one); - assertEqual(reText, text); + assertEqual(reText, text, width, height); } void DeltaTests::testRandomDeltas()