21static constexpr uint8_t DBZV_VERSION_HIGH = 0;
22static constexpr uint8_t DBZV_VERSION_LOW = 1;
23static constexpr uint8_t COMPRESSION_ZLIB = 1;
24static constexpr unsigned MAX_VECTOR = 16;
25static constexpr unsigned BLOCK_WIDTH = MAX_VECTOR;
26static constexpr unsigned BLOCK_HEIGHT = MAX_VECTOR;
27static constexpr unsigned FLAG_KEYFRAME = 0x01;
34static constexpr unsigned VECTOR_TAB_SIZE =
37 MAX_VECTOR * MAX_VECTOR - 2 * MAX_VECTOR;
39static constexpr auto vectorTable = [] {
40 std::array<CodecVector, VECTOR_TAB_SIZE> result = {};
47 for (
int i = 1; i <= int(MAX_VECTOR); ++i, p += 8) {
48 result[p + 0] = {int8_t( i), int8_t( 0)};
49 result[p + 1] = {int8_t(-i), int8_t( 0)};
50 result[p + 2] = {int8_t( 0), int8_t( i)};
51 result[p + 3] = {int8_t( 0), int8_t(-i)};
52 result[p + 4] = {int8_t( i), int8_t( i)};
53 result[p + 5] = {int8_t(-i), int8_t( i)};
54 result[p + 6] = {int8_t( i), int8_t(-i)};
55 result[p + 7] = {int8_t(-i), int8_t(-i)};
58 for (
int y = 1; y <= int(MAX_VECTOR / 2); ++y) {
59 for (
int x = 1; x <= int(MAX_VECTOR / 2); ++x) {
61 result[p + 0] = {int8_t( x), int8_t( y)};
62 result[p + 1] = {int8_t(-x), int8_t( y)};
63 result[p + 2] = {int8_t( x), int8_t(-y)};
64 result[p + 3] = {int8_t(-x), int8_t(-y)};
68 assert(p == VECTOR_TAB_SIZE);
71 auto compare = [](
const CodecVector& l,
const CodecVector& r) {
72 auto cost = [](
const CodecVector& v) {
73 auto c =
cstd::sqrt(
double(v.x * v.x + v.y * v.y));
74 if ((v.x == 0) || (v.y == 0)) {
86 return std::tuple(cost(l), l.x, l.y) <
87 std::tuple(cost(r), r.x, r.y);
108 unsigned r = pixelOps.
red256(pixel);
110 unsigned b = pixelOps.
blue256(pixel);
111 dest = narrow<uint16_t>(((r & 0xF8) << (11 - 3)) | ((
g & 0xFC) << (5 - 2)) | (b >> 3));
115 const PixelOperations<unsigned>& pixelOps,
118 unsigned r = pixelOps.red256(pixel);
119 unsigned g = pixelOps.green256(pixel);
120 unsigned b = pixelOps.blue256(pixel);
121 dest = (r << 16) | (
g << 8) | b;
130 memset(&zstream, 0,
sizeof(zstream));
131 deflateInit(&zstream, 6);
152void ZMBVEncoder::setupBuffers(
unsigned bpp)
158 format = ZMBV_FORMAT_16BPP;
164 format = ZMBV_FORMAT_32BPP;
172 pitch = width + 2 * MAX_VECTOR;
173 auto bufSize = (height + 2 * MAX_VECTOR) * pitch * pixelSize + 2048;
180 outputSize = neededSize();
181 output.
resize(outputSize);
183 assert((width % BLOCK_WIDTH ) == 0);
184 assert((height % BLOCK_HEIGHT) == 0);
185 size_t xBlocks = width / BLOCK_WIDTH;
186 size_t yBlocks = height / BLOCK_HEIGHT;
187 blockOffsets.
resize(xBlocks * yBlocks);
188 for (
auto y :
xrange(yBlocks)) {
189 for (
auto x :
xrange(xBlocks)) {
190 blockOffsets[y * xBlocks + x] =
191 ((y * BLOCK_HEIGHT) + MAX_VECTOR) * pitch +
192 (x * BLOCK_WIDTH) + MAX_VECTOR;
197unsigned ZMBVEncoder::neededSize()
const
199 unsigned f = pixelSize;
200 f = f * width * height + 2 * (1 + (width / 8)) * (1 + (height / 8)) + 1024;
204template<std::
unsigned_
integral P>
205unsigned ZMBVEncoder::possibleBlock(
int vx,
int vy,
size_t offset)
208 auto* pOld = &(
reinterpret_cast<P*
>(oldFrame.
data()))[offset + (vy * pitch) + vx];
209 auto* pNew = &(
reinterpret_cast<P*
>(newFrame.
data()))[offset];
210 for (
unsigned y = 0; y < BLOCK_HEIGHT; y += 4) {
211 for (
unsigned x = 0; x < BLOCK_WIDTH; x += 4) {
212 if (pOld[x] != pNew[x]) ++ret;
220template<std::
unsigned_
integral P>
221unsigned ZMBVEncoder::compareBlock(
int vx,
int vy,
size_t offset)
224 auto* pOld = &(
reinterpret_cast<P*
>(oldFrame.
data()))[offset + (vy * pitch) + vx];
225 auto* pNew = &(
reinterpret_cast<P*
>(newFrame.
data()))[offset];
226 repeat(BLOCK_HEIGHT, [&] {
227 for (
auto x :
xrange(BLOCK_WIDTH)) {
228 if (pOld[x] != pNew[x]) ++ret;
236template<std::
unsigned_
integral P>
237void ZMBVEncoder::addXorBlock(
238 const PixelOperations<P>& pixelOps,
int vx,
int vy,
size_t offset,
unsigned& workUsed)
242 auto* pOld = &(
reinterpret_cast<P*
>(oldFrame.
data()))[offset + (vy * pitch) + vx];
243 auto* pNew = &(
reinterpret_cast<P*
>(newFrame.
data()))[offset];
244 repeat(BLOCK_HEIGHT, [&] {
245 for (
auto x :
xrange(BLOCK_WIDTH)) {
246 P pXor = pNew[x] ^ pOld[x];
247 writePixel(pixelOps, pXor, *
reinterpret_cast<LE_P*
>(&work[workUsed]));
248 workUsed +=
sizeof(P);
255template<std::
unsigned_
integral P>
256void ZMBVEncoder::addXorFrame(
const PixelFormat& pixelFormat,
unsigned& workUsed)
258 PixelOperations<P> pixelOps(pixelFormat);
259 auto* vectors =
reinterpret_cast<int8_t*
>(&work[workUsed]);
261 unsigned xBlocks = width / BLOCK_WIDTH;
262 unsigned yBlocks = height / BLOCK_HEIGHT;
263 unsigned blockCount = xBlocks * yBlocks;
266 workUsed = (workUsed + blockCount * 2 + 3) & ~3;
270 for (
auto b :
xrange(blockCount)) {
271 auto offset = blockOffsets[b];
273 unsigned bestChange = compareBlock<P>(bestVx, bestVy, offset);
274 if (bestChange >= 4) {
276 for (
const auto& v : vectorTable) {
277 if (possibleBlock<P>(v.x, v.y, offset) < 4) {
278 unsigned testChange = compareBlock<P>(v.x, v.y, offset);
279 if (testChange < bestChange) {
280 bestChange = testChange;
281 bestVx = narrow<int>(v.x);
282 bestVy = narrow<int>(v.y);
283 if (bestChange < 4)
break;
286 if (possibles == 0)
break;
290 vectors[b * 2 + 0] = narrow<int8_t>(bestVx << 1);
291 vectors[b * 2 + 1] = narrow<int8_t>(bestVy << 1);
293 vectors[b * 2 + 0] |= 1;
294 addXorBlock<P>(pixelOps, bestVx, bestVy, offset, workUsed);
299template<std::
unsigned_
integral P>
300void ZMBVEncoder::addFullFrame(
const PixelFormat& pixelFormat,
unsigned& workUsed)
304 PixelOperations<P> pixelOps(pixelFormat);
306 &newFrame[pixelSize * (MAX_VECTOR + MAX_VECTOR * pitch)];
308 auto* pixelsIn =
reinterpret_cast<P*
> (readFrame);
309 auto* pixelsOut =
reinterpret_cast<LE_P*
>(&work[workUsed]);
310 for (
auto x :
xrange(width)) {
311 writePixel(pixelOps, pixelsIn[x], pixelsOut[x]);
313 readFrame += pitch *
sizeof(P);
314 workUsed += narrow<unsigned>(width *
sizeof(P));
318const void* ZMBVEncoder::getScaledLine(FrameSource* frame,
unsigned y,
void* workBuf_)
const
321 if (pixelSize == 4) {
322 auto* workBuf =
static_cast<uint32_t*
>(workBuf_);
325 return frame->getLinePtr320_240(y, std::span<uint32_t, 320>(workBuf, 320)).data();
327 return frame->getLinePtr640_480(y, std::span<uint32_t, 640>(workBuf, 640)).data();
329 return frame->getLinePtr960_720(y, std::span<uint32_t, 960>(workBuf, 960)).data();
336 if (pixelSize == 2) {
337 auto* workBuf =
static_cast<uint16_t*
>(workBuf_);
340 return frame->getLinePtr320_240(y, std::span<uint16_t, 320>(workBuf, 320)).data();
342 return frame->getLinePtr640_480(y, std::span<uint16_t, 640>(workBuf, 640)).data();
344 return frame->getLinePtr960_720(y, std::span<uint16_t, 960>(workBuf, 960)).data();
359 unsigned workUsed = 0;
360 unsigned writeDone = 1;
361 uint8_t* writeBuf = output.
data();
365 output[0] |= FLAG_KEYFRAME;
367 writeBuf + writeDone);
369 header->low_version = DBZV_VERSION_LOW;
370 header->compression = COMPRESSION_ZLIB;
371 header->format = format;
372 header->blockWidth = BLOCK_WIDTH;
373 header->blockHeight = BLOCK_HEIGHT;
375 deflateReset(&zstream);
379 auto linePitch = pitch * pixelSize;
380 auto lineWidth = size_t(width) * pixelSize;
382 &newFrame[pixelSize * (MAX_VECTOR + MAX_VECTOR * pitch)];
383 for (
auto i :
xrange(height)) {
384 const auto* scaled = getScaledLine(frame, i, dest);
385 if (scaled != dest) memcpy(dest, scaled, lineWidth);
424 zstream.next_in = work.
data();
425 zstream.avail_in = workUsed;
426 zstream.total_in = 0;
428 zstream.next_out =
static_cast<Bytef*
>(writeBuf + writeDone);
429 zstream.avail_out = outputSize - writeDone;
430 zstream.total_out = 0;
431 auto r = deflate(&zstream, Z_SYNC_FLUSH);
432 assert(r == Z_OK); (void)r;
434 return {output.
data(), writeDone + zstream.total_out};
Interface for getting lines from a video frame.
const PixelFormat & getPixelFormat() const
void resize(size_t size)
Grow or shrink the memory block.
const T * data() const
Returns pointer to the start of the memory buffer.
unsigned blue256(Pixel p) const
unsigned red256(Pixel p) const
Same as above, but result is scaled to [0..255].
unsigned green256(Pixel p) const
std::span< const uint8_t > compressFrame(bool keyFrame, FrameSource *frame)
ZMBVEncoder(unsigned width, unsigned height, unsigned bpp)
constexpr double sqrt(double x)
This file implemented 3 utility functions:
Pixel writePixel(uint32_t p)
constexpr void fill(ForwardRange &&range, const T &value)
constexpr void sort(RandomAccessRange &&range)
void swap(openmsx::MemBuffer< T > &l, openmsx::MemBuffer< T > &r) noexcept
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
constexpr auto xrange(T e)