2017-09-16 11:27:31 -05:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
/*
|
|
|
|
* This file is part of the LibreOffice project.
|
|
|
|
*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*/
|
|
|
|
#ifndef INCLUDED_DELTA_HPP
|
|
|
|
#define INCLUDED_DELTA_HPP
|
|
|
|
|
|
|
|
#include <vector>
|
2017-09-16 11:32:20 -05:00
|
|
|
#include <assert.h>
|
|
|
|
#include <Log.hpp>
|
2017-09-16 11:27:31 -05:00
|
|
|
|
2017-09-16 11:32:20 -05:00
|
|
|
#ifndef TILE_WIRE_ID
|
2017-09-16 11:27:31 -05:00
|
|
|
# define TILE_WIRE_ID
|
|
|
|
typedef uint32_t TileWireId;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/// A quick and dirty delta generator for last tile changes
|
|
|
|
class DeltaGenerator {
|
|
|
|
|
2017-09-28 03:45:46 -05:00
|
|
|
struct DeltaBitmapRow {
|
|
|
|
uint64_t _crc;
|
|
|
|
std::vector<uint32_t> _pixels;
|
|
|
|
|
|
|
|
bool identical(const DeltaBitmapRow &other) const
|
|
|
|
{
|
|
|
|
if (_crc != other._crc)
|
|
|
|
return false;
|
|
|
|
return _pixels == other._pixels;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-09-16 11:27:31 -05:00
|
|
|
struct DeltaData {
|
2018-11-07 02:09:01 -06:00
|
|
|
void setWid(TileWireId wid)
|
|
|
|
{
|
|
|
|
_wid = wid;
|
|
|
|
}
|
|
|
|
|
|
|
|
TileWireId getWid() const
|
|
|
|
{
|
|
|
|
return _wid;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setWidth(int width)
|
|
|
|
{
|
|
|
|
_width = width;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getWidth() const
|
|
|
|
{
|
|
|
|
return _width;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setHeight(int height)
|
|
|
|
{
|
|
|
|
_height = height;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getHeight() const
|
|
|
|
{
|
|
|
|
return _height;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::vector<DeltaBitmapRow>& getRows() const
|
|
|
|
{
|
|
|
|
return _rows;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<DeltaBitmapRow>& getRows()
|
|
|
|
{
|
|
|
|
return _rows;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2017-09-16 11:27:31 -05:00
|
|
|
TileWireId _wid;
|
2017-09-28 03:45:46 -05:00
|
|
|
int _width;
|
|
|
|
int _height;
|
|
|
|
std::vector<DeltaBitmapRow> _rows;
|
2017-09-16 11:27:31 -05:00
|
|
|
};
|
2017-09-28 03:45:46 -05:00
|
|
|
std::vector<std::shared_ptr<DeltaData>> _deltaEntries;
|
2017-09-16 11:27:31 -05:00
|
|
|
|
|
|
|
bool makeDelta(
|
2017-09-28 03:45:46 -05:00
|
|
|
const DeltaData &prev,
|
|
|
|
const DeltaData &cur,
|
2017-09-16 11:27:31 -05:00
|
|
|
std::vector<char>& output)
|
|
|
|
{
|
2017-09-28 03:45:46 -05:00
|
|
|
// TODO: should we split and compress alpha separately ?
|
2018-11-07 02:09:01 -06:00
|
|
|
if (prev.getWidth() != cur.getWidth() || prev.getHeight() != cur.getHeight())
|
2017-09-16 11:27:31 -05:00
|
|
|
{
|
2018-11-07 02:09:01 -06:00
|
|
|
LOG_ERR("mis-sized delta: " << prev.getWidth() << "x" << prev.getHeight() << " vs "
|
|
|
|
<< cur.getWidth() << "x" << cur.getHeight());
|
2017-09-16 11:27:31 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
output.push_back('D');
|
2018-11-07 02:09:01 -06:00
|
|
|
LOG_TRC("building delta of a " << cur.getWidth() << "x" << cur.getHeight() << " bitmap");
|
2017-09-28 03:45:46 -05:00
|
|
|
|
|
|
|
// row move/copy src/dest is a byte.
|
2018-11-07 02:09:01 -06:00
|
|
|
assert (prev.getHeight() <= 256);
|
2017-09-28 03:45:46 -05:00
|
|
|
// column position is a byte.
|
2018-11-07 02:09:01 -06:00
|
|
|
assert (prev.getWidth() <= 256);
|
2017-09-28 03:45:46 -05:00
|
|
|
|
|
|
|
// How do the rows look against each other ?
|
|
|
|
size_t lastMatchOffset = 0;
|
2017-10-23 14:59:10 -05:00
|
|
|
size_t lastCopy = 0;
|
2018-11-07 02:09:01 -06:00
|
|
|
for (int y = 0; y < prev.getHeight(); ++y)
|
2017-09-16 11:27:31 -05:00
|
|
|
{
|
2017-09-28 03:45:46 -05:00
|
|
|
// Life is good where rows match:
|
2018-11-07 02:09:01 -06:00
|
|
|
if (prev.getRows()[y].identical(cur.getRows()[y]))
|
2017-09-28 03:45:46 -05:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// Hunt for other rows
|
|
|
|
bool matched = false;
|
2018-11-07 02:09:01 -06:00
|
|
|
for (int yn = 0; yn < prev.getHeight() && !matched; ++yn)
|
2017-09-16 11:27:31 -05:00
|
|
|
{
|
2018-11-07 02:09:01 -06:00
|
|
|
size_t match = (y + lastMatchOffset + yn) % prev.getHeight();
|
|
|
|
if (prev.getRows()[match].identical(cur.getRows()[y]))
|
2017-09-16 11:27:31 -05:00
|
|
|
{
|
2017-09-28 03:45:46 -05:00
|
|
|
// TODO: if offsets are >256 - use 16bits?
|
2017-10-23 14:59:10 -05:00
|
|
|
if (lastCopy > 0)
|
|
|
|
{
|
|
|
|
char cnt = output[lastCopy];
|
|
|
|
if (output[lastCopy + 1] + cnt == (char)(match) &&
|
|
|
|
output[lastCopy + 2] + cnt == (char)(y))
|
|
|
|
{
|
|
|
|
output[lastCopy]++;
|
|
|
|
matched = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2017-09-28 03:45:46 -05:00
|
|
|
|
|
|
|
lastMatchOffset = match - y;
|
2017-10-23 14:59:10 -05:00
|
|
|
output.push_back('c'); // copy-row
|
|
|
|
lastCopy = output.size();
|
|
|
|
output.push_back(1); // count
|
2017-09-28 03:45:46 -05:00
|
|
|
output.push_back(match); // src
|
2017-10-23 14:59:10 -05:00
|
|
|
output.push_back(y); // dest
|
|
|
|
|
2017-09-28 03:45:46 -05:00
|
|
|
matched = true;
|
|
|
|
continue;
|
2017-09-16 11:27:31 -05:00
|
|
|
}
|
|
|
|
}
|
2017-09-28 03:45:46 -05:00
|
|
|
if (matched)
|
|
|
|
continue;
|
2017-09-16 11:27:31 -05:00
|
|
|
|
2017-09-28 03:45:46 -05:00
|
|
|
// Our row is just that different:
|
2018-11-07 02:09:01 -06:00
|
|
|
const DeltaBitmapRow &curRow = cur.getRows()[y];
|
|
|
|
const DeltaBitmapRow &prevRow = prev.getRows()[y];
|
|
|
|
for (int x = 0; x < prev.getWidth();)
|
2017-09-16 11:27:31 -05:00
|
|
|
{
|
2017-09-28 03:45:46 -05:00
|
|
|
int same;
|
2018-11-07 02:09:01 -06:00
|
|
|
for (same = 0; same + x < prev.getWidth() &&
|
2017-09-28 03:45:46 -05:00
|
|
|
prevRow._pixels[x+same] == curRow._pixels[x+same];)
|
|
|
|
++same;
|
|
|
|
|
|
|
|
x += same;
|
|
|
|
|
|
|
|
int diff;
|
2018-11-07 02:09:01 -06:00
|
|
|
for (diff = 0; diff + x < prev.getWidth() &&
|
2017-09-28 03:45:46 -05:00
|
|
|
(prevRow._pixels[x+diff] == curRow._pixels[x+diff] || diff < 2) &&
|
|
|
|
diff < 254;)
|
|
|
|
++diff;
|
|
|
|
if (diff > 0)
|
2017-09-16 11:27:31 -05:00
|
|
|
{
|
2017-09-28 03:45:46 -05:00
|
|
|
output.push_back('d');
|
|
|
|
output.push_back(y);
|
|
|
|
output.push_back(x);
|
|
|
|
output.push_back(diff);
|
|
|
|
|
|
|
|
size_t dest = output.size();
|
|
|
|
output.resize(dest + diff * 4);
|
|
|
|
memcpy(&output[dest], &curRow._pixels[x], diff * 4);
|
2017-09-16 11:27:31 -05:00
|
|
|
|
2017-09-28 03:45:46 -05:00
|
|
|
LOG_TRC("different " << diff << "pixels");
|
|
|
|
x += diff;
|
|
|
|
}
|
2017-09-16 11:27:31 -05:00
|
|
|
}
|
|
|
|
}
|
2017-09-28 03:45:46 -05:00
|
|
|
|
2017-09-16 11:27:31 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-09-28 03:45:46 -05:00
|
|
|
std::shared_ptr<DeltaData> dataToDeltaData(
|
|
|
|
TileWireId wid,
|
2017-09-16 11:27:31 -05:00
|
|
|
unsigned char* pixmap, size_t startX, size_t startY,
|
|
|
|
int width, int height,
|
|
|
|
int bufferWidth, int bufferHeight)
|
|
|
|
{
|
2017-09-28 03:45:46 -05:00
|
|
|
auto data = std::make_shared<DeltaData>();
|
2018-11-07 02:09:01 -06:00
|
|
|
data->setWid(wid);
|
2017-09-28 03:45:46 -05:00
|
|
|
|
2017-09-16 11:27:31 -05:00
|
|
|
assert (startX + width <= (size_t)bufferWidth);
|
|
|
|
assert (startY + height <= (size_t)bufferHeight);
|
|
|
|
|
2017-11-23 08:01:25 -06:00
|
|
|
(void)bufferHeight;
|
|
|
|
|
2017-09-28 03:45:46 -05:00
|
|
|
LOG_TRC("Converting pixel data to delta data of size "
|
2017-09-16 11:27:31 -05:00
|
|
|
<< (width * height * 4) << " width " << width
|
|
|
|
<< " height " << height);
|
|
|
|
|
2018-11-07 02:09:01 -06:00
|
|
|
data->setWidth(width);
|
|
|
|
data->setHeight(height);
|
|
|
|
data->getRows().resize(height);
|
2017-09-16 11:27:31 -05:00
|
|
|
for (int y = 0; y < height; ++y)
|
|
|
|
{
|
2018-11-07 02:09:01 -06:00
|
|
|
DeltaBitmapRow &row = data->getRows()[y];
|
2017-09-16 11:27:31 -05:00
|
|
|
size_t position = ((startY + y) * bufferWidth * 4) + (startX * 4);
|
2017-09-28 03:45:46 -05:00
|
|
|
int32_t *src = reinterpret_cast<int32_t *>(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];
|
|
|
|
}
|
2017-09-16 11:27:31 -05:00
|
|
|
}
|
|
|
|
|
2017-09-28 03:45:46 -05:00
|
|
|
return data;
|
2017-09-16 11:27:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
DeltaGenerator() {}
|
|
|
|
|
|
|
|
/**
|
2017-09-16 11:32:20 -05:00
|
|
|
* Creates a delta between @oldWid and pixmap if possible:
|
2017-09-16 11:27:31 -05:00
|
|
|
* if so - returns @true and appends the delta to @output
|
|
|
|
* stores @pixmap, and other data to accelerate delta
|
|
|
|
* creation in a limited size cache.
|
|
|
|
*/
|
|
|
|
bool createDelta(
|
|
|
|
unsigned char* pixmap, size_t startX, size_t startY,
|
|
|
|
int width, int height,
|
|
|
|
int bufferWidth, int bufferHeight,
|
|
|
|
std::vector<char>& output,
|
|
|
|
TileWireId wid, TileWireId oldWid)
|
|
|
|
{
|
|
|
|
// First store a copy for later:
|
|
|
|
if (_deltaEntries.size() > 6) // FIXME: hard-coded ...
|
|
|
|
_deltaEntries.erase(_deltaEntries.begin());
|
|
|
|
|
2017-09-28 03:45:46 -05:00
|
|
|
std::shared_ptr<DeltaData> update =
|
|
|
|
dataToDeltaData(wid, pixmap, startX, startY, width, height,
|
|
|
|
bufferWidth, bufferHeight);
|
2017-09-16 11:27:31 -05:00
|
|
|
_deltaEntries.push_back(update);
|
|
|
|
|
|
|
|
for (auto &old : _deltaEntries)
|
|
|
|
{
|
2018-11-07 02:09:01 -06:00
|
|
|
if (oldWid == old->getWid())
|
2017-09-28 03:45:46 -05:00
|
|
|
return makeDelta(*old, *update, output);
|
2017-09-16 11:27:31 -05:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|