/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright (C) 2011 D. R. Commander.  All Rights Reserved.
 * Copyright 2014 Pierre Ossman for Cendio AB
 * Copyright (C) 2015-2026 m-privacy GmbH.  All Rights Reserved.
 * 
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

#include <rfb/MPCompressor.h>
#include <rdr/Exception.h>
#include <rfb/Rect.h>
#include <rfb/PixelFormat.h>
#include <rfb/ClientParams.h>
#include <rfb/Configuration.h>

#include <stdio.h>

#if !defined(__APPLE__) && !defined(WIN32)
#include <x264.h>
#include <sys/random.h>
#include <wels/codec_api.h>
#endif
#include <libyuv/convert.h>
#include <libyuv/convert_from_argb.h>

extern "C" {
#include <jpeglib.h>
}
#include <setjmp.h>
#include <zstd.h>

#include <cassert>

using namespace rfb;

static rfb::LogWriter vlog("MPCompressor");

IntParameter pressureLevelIncrease("PressureLevelIncrease", "CPU pressure above which to raise pressureLevel", 20, 1, 99);
IntParameter pressureLevelDecrease("PressureLevelDecrease", "CPU pressure below which to reduce pressureLevel", 10, 1, 99);
IntParameter pressureLevelClear("PressureLevelClear", "CPU pressure below which to reset pressureLevel to 0", 3, 1, 99);
IntParameter VNCZstdLevel("VNCZstdLevel", "VNC ZSTD compression level (0-22, 0 for ZSTD default (currently 3))", 0, 0, 22);
BoolParameter MPOptRectSize("MPOptRectSize", "Always try to minimize MP rect sizes", false);
#if !defined(__APPLE__)
IntParameter jpegxlEffort("JPEGXLEffort", "Dummy: JPEGXL normal encoder effort/speed (1-9, 1:lightning 2:thunder 3:falcon 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise)", 2, 1, 9);
IntParameter jpegxlDecodingSpeed("JPEGXLDecodingSpeed", "Dummy: JPEGXL decoding speed (0-4, 0 slowest, best quality, 4 fastest)", 0, 0, 4);
IntParameter jpegxlDistance("JPEGXLDistance", "Dummy: JPEGXL distance (0-15, 0 lossless, 15 fastest)", 1, 0, 15);
#if !defined(WIN32)
IntParameter autoLbH264Rate("AutoLbH264Rate", "Rate in n/10 to mix H264 under JPEGLB in Save Bandwidth High mode (0-10, 0 none, 10 all)", 10, 0, 10);
IntParameter autoLbH264MinSizeK("AutoLbH264MinSizeK", "Min Rect size for H264 in Save Bandwidth High mode in KPixel (0-999)", 10, 0, 999);
BoolParameter jpegxlMultiThread("JPEGXLMultiThread", "Dummy: Enable internal multi threading in libjxl on top of our own multi threading", true);
IntParameter x264Speedup("X264Speedup", "Improve libx264 encoding speed at the cost of quality: 0=medium, 1=fast, 2=faster, 3=veryfast, 4=superfast, 5=ultrafast  (0-5)", 0, 0, 5);
IntParameter x264CRF("X264CRF", "libx264 quality, better quality means higher bandwidth (10 (very good) - 30 (very low))", 20, 10, 30);
#endif
#endif

#define MAXPRESSURELEVEL 10

struct TightMPJpegConfiguration {
	int quality;
};

// NOTE: The MP quality and subsampling levels below were obtained
// experimentally by the VirtualGL Project. They represent the approximate
// average compression ratios listed below, as measured across the set of
// every 10th frame in the SPECviewperf 9 benchmark suite.
// Note: Taken from JPEG encoder
// 9 = MP quality 100, no subsampling (ratio ~= 10:1)
//     [this should be lossless, except for round-off error]
// 8 = MP quality 92,  no subsampling (ratio ~= 20:1)
//     [this should be perceptually lossless, based on current research]
// 7 = MP quality 86,  no subsampling (ratio ~= 25:1)
// 6 = MP quality 79,  no subsampling (ratio ~= 30:1)
// 5 = MP quality 77,  4:2:2 subsampling (ratio ~= 40:1)
// 4 = MP quality 62,  4:2:2 subsampling (ratio ~= 50:1)
// 3 = MP quality 42,  4:2:2 subsampling (ratio ~= 60:1)
// 2 = MP quality 41,  4:2:0 subsampling (ratio ~= 70:1)
// 1 = MP quality 29,  4:2:0 subsampling (ratio ~= 80:1)
// 0 = MP quality 15,  4:2:0 subsampling (ratio ~= 100:1)

static const struct TightMPJpegConfiguration jpegConf[10] = {
	{  15 }, // 0
	{  29 }, // 1
	{  41 }, // 2
	{  42 }, // 3
	{  62 }, // 4
	{  77 }, // 5
	{  79 }, // 6
	{  86 }, // 7
	{  92 }, // 8
	{ 100 }  // 9
};

rdr::U8 MPCompressor::cpuPressure = 0;
rdr::U8 MPCompressor::pressureLevel = 0;
bool MPCompressor::saveBandwidth = false;
bool MPCompressor::saveBandwidthHigh = false;
#if !defined(__APPLE__) && !defined(WIN32)
bool MPCompressor::clientSupportsTightMPX264 = false;
#endif

void MPCompressor::setCpuPressure(rdr::U8 value)
{
  vlog.verbose("setCpuPressure(): got value %u", value);
  cpuPressure = value;
  if (value > pressureLevelIncrease) {
    if (pressureLevel < MAXPRESSURELEVEL) {
      pressureLevel++;
      vlog.debug("setCpuPressure(): value %u above %s, increase pressureLevel to %u", value, pressureLevelIncrease.getValueStr(), pressureLevel);
    } else {
      vlog.debug("setCpuPressure(): value %u above %s, but pressureLevel already at max %u", value, pressureLevelIncrease.getValueStr(), MAXPRESSURELEVEL);
    }
  } else if (pressureLevel > 0) {
    if (value < pressureLevelClear) {
      pressureLevel = 0;
      vlog.debug("setCpuPressure(): value below %s, reset pressureLevel to 0", pressureLevelClear.getValueStr());
    } else if (value < pressureLevelDecrease){
      pressureLevel--;
      vlog.debug("setCpuPressure(): value %u below %s, decrease pressureLevel to %u", value, pressureLevelDecrease.getValueStr(), pressureLevel);
    } else {
      vlog.debug("setCpuPressure(): value %u between %s and %s, keep pressureLevel at %u", value, pressureLevelDecrease.getValueStr(), pressureLevelIncrease.getValueStr(), pressureLevel);
    }
  }
}

void MPCompressor::setSaveBandwidth(bool value)
{
  if (value != saveBandwidth)
    vlog.debug("setSaveBandwidth(): change value to %u", value);
  saveBandwidth = value;
}

void MPCompressor::setSaveBandwidthHigh(bool value)
{
  if (value != saveBandwidthHigh)
    vlog.debug("setSaveBandwidthHigh(): change value to %u", value);
  saveBandwidthHigh = value;
}

#if !defined(__APPLE__) && !defined(WIN32)
void MPCompressor::setSupportsTightMPX264(bool value)
{
  if (value != clientSupportsTightMPX264)
    vlog.debug("setSupportsTightMPX264(): change value to %u", value);
  clientSupportsTightMPX264 = value;
}
#endif

/* JPEG code ported from JpegCompressor */
struct MP_JPEG_ERROR_MGR {
  struct jpeg_error_mgr pub;
  jmp_buf jmpBuffer;
  char lastError[JMSG_LENGTH_MAX];
};

static void
JpegErrorExit(j_common_ptr jpegCinfo)
{
  MP_JPEG_ERROR_MGR *jpegErr = (MP_JPEG_ERROR_MGR *)jpegCinfo->err;

  (*jpegCinfo->err->output_message)(jpegCinfo);
  longjmp(jpegErr->jmpBuffer, 1);
}

static void
JpegOutputMessage(j_common_ptr jpegCinfo)
{
  MP_JPEG_ERROR_MGR *jpegErr = (MP_JPEG_ERROR_MGR *)jpegCinfo->err;

  (*jpegCinfo->err->format_message)(jpegCinfo, jpegErr->lastError);
}

struct MP_JPEG_DEST_MGR {
  struct jpeg_destination_mgr pub;
  MPCompressor *instance;
  size_t chunkSize;
};

static void
JpegInitDestination(j_compress_ptr jpegCinfo)
{
  MP_JPEG_DEST_MGR *jpegDest = (MP_JPEG_DEST_MGR *)jpegCinfo->dest;
  MPCompressor *mpc = jpegDest->instance;

  jpegDest->pub.next_output_byte = (JOCTET *) mpc->getptr(mpc->length());
  jpegDest->pub.free_in_buffer = jpegDest->chunkSize = mpc->avail();
}

static boolean
JpegEmptyOutputBuffer(j_compress_ptr jpegCinfo)
{
  MP_JPEG_DEST_MGR *jpegDest = (MP_JPEG_DEST_MGR *)jpegCinfo->dest;
  MPCompressor *mpc = jpegDest->instance;

  mpc->setptr(mpc->avail());
  jpegDest->pub.next_output_byte = mpc->getptr(mpc->length());
  jpegDest->pub.free_in_buffer = jpegDest->chunkSize = mpc->avail();
  return TRUE;
}

static void
JpegTermDestination(j_compress_ptr jpegCinfo)
{
  MP_JPEG_DEST_MGR *jpegDest = (MP_JPEG_DEST_MGR *)jpegCinfo->dest;
  MPCompressor *mpc = jpegDest->instance;

  mpc->setptr(jpegDest->chunkSize - jpegDest->pub.free_in_buffer);
}

/* convert RGBA to I444, based on IYUV 4:2:0 with 12 Bit/pixel, planar
 * I420: 1(Y) + 1/4(U) + 1/4(V)
 * uneven width or height need extra column or line
 * see https://www.fourcc.org/pixel-format/yuv-i420/
 * code inspired from:
 * https://stackoverflow.com/questions/9465815/rgb-to-yuv420-algorithm-efficiency
 * formula from
 * https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering
 * https://de.wikipedia.org/wiki/YUV-Farbmodell
 * I444: 1(Y) + 1(U) + 1(V)
 */
static inline rdr::U8 calcY(rdr::U8 r, rdr::U8 g, rdr::U8 b)
{
	return ((66*r + 129*g + 25*b + 128) >> 8) + 16;
}
static inline rdr::U8 calcU(rdr::U8 r, rdr::U8 g, rdr::U8 b)
{
	return ((-38*r + -74*g + 112*b + 128) >> 8) + 128;
}
static inline rdr::U8 calcV(rdr::U8 r, rdr::U8 g, rdr::U8 b)
{
	return ((112*r + -94*g + -18*b + 128) >> 8) + 128;
}

static void rgbaToI444(const rdr::U8 *src, const int srcStride, rdr::U8 *destY, const int destStrideY,
			rdr::U8 *destU, const int destStrideU, rdr::U8 *destV, const int destStrideV,
			const int width, const int height)
{
	const int pixelsize = 4;

	#pragma GCC ivdep
	for( int y = 0; y < height; y++ ) {
		const size_t lineSrc = y * srcStride;
		const size_t lineY = y * destStrideY;
		const size_t lineU = y * destStrideU;
		const size_t lineV = y * destStrideV;

		#pragma GCC ivdep
		for( int x = 0; x < width; x++ ) {
			const rdr::U8 * srcxpos = src + lineSrc + pixelsize * x;
			const rdr::U8 r = *srcxpos;
			const rdr::U8 g = *(srcxpos + 1);
			const rdr::U8 b = *(srcxpos + 2);
			*(destY+lineY+x) = calcY(r, g, b);
			*(destU+lineU+x) = calcU(r, g, b);
			*(destV+lineV+x) = calcV(r, g, b);
		}
	}
}


MPCompressor::MPCompressor(int bufferLen) : MemOutStream(bufferLen),
#if !defined(__APPLE__) && !defined(WIN32)
	welsVideoFormat(videoFormatI420), welsComplexity(MEDIUM_COMPLEXITY),
#endif
	zstdCctx(NULL)
{
	classLog = &vlog;
	classMemLog = &vlog;

	dataBuffer = NULL;
	dataBufferLen = 0;

#pragma GCC ivdep
	for (int i = MP_COMPRESSION_MIN; i <= MP_COMPRESSION_MAX; i++) {
		compressionCount[i] = 0;
		optimizationCount[i] = 0;
		totalBytes[i] = 0;
		totalCBytes[i] = 0;
		minCompressed[i] = 100000;
		maxCompressed[i] = 0;
	}

#if !defined(__APPLE__) && !defined(WIN32)
	memset (&welsParam, 0, sizeof(welsParam));
	welsParam.iUsageType = SCREEN_CONTENT_REAL_TIME;
	welsParam.fMaxFrameRate = 24;
	welsParam.iTargetBitrate = 5000000;

	/* Set default params */
	x264_param_default(&x264_param);
	switch(x264Speedup) {
		case 1:
			x264_param_default_preset(&x264_param, "fast", "stillimage");
			break;
		case 2:
			x264_param_default_preset(&x264_param, "faster", "stillimage");
			break;
		case 3:
			x264_param_default_preset(&x264_param, "veryfast", "stillimage");
			break;
		case 4:
			x264_param_default_preset(&x264_param, "superfast", "stillimage");
			break;
		case 5:
			x264_param_default_preset(&x264_param, "ultrafast", "stillimage");
			break;
		default:
			x264_param_default_preset(&x264_param, "medium", "stillimage");
	}
	/* Configure non-default params */
	x264_param.i_bitdepth = 8;
	x264_param.i_csp = X264_CSP_I444;
	x264_param.i_frame_total = 1;
	x264_param.b_vfr_input = 0;
	x264_param.b_repeat_headers = 1;
	x264_param.b_annexb = 1;
	x264_param.b_opencl = 1;
	/* Constant Rate Factor, recommended values are 18 (good) to 28 (less good) */
	x264_param.rc.i_rc_method = X264_RC_CRF;
	x264_param.rc.f_rf_constant = x264CRF;
//	x264_param.i_log_level = X264_LOG_DEBUG;
	if (x264_param_apply_profile(&x264_param, "high") < 0)
		vlog.error("x264_param_apply_profile() failed");
#endif

	jpegCinfo = new jpeg_compress_struct;
	jpegErr = new struct MP_JPEG_ERROR_MGR;
	jpegCinfo->err = jpeg_std_error(&jpegErr->pub);
	snprintf(jpegErr->lastError, JMSG_LENGTH_MAX, "No error");
	jpegErr->pub.error_exit = JpegErrorExit;
	jpegErr->pub.output_message = JpegOutputMessage;
	if(unlikely(setjmp(jpegErr->jmpBuffer))) {
		// this will execute if libjpeg has an error
		throw rdr::Exception("%s", jpegErr->lastError);
	}
	jpeg_create_compress(jpegCinfo);
	jpegDest = new struct MP_JPEG_DEST_MGR;
	jpegDest->pub.init_destination = JpegInitDestination;
	jpegDest->pub.empty_output_buffer = JpegEmptyOutputBuffer;
	jpegDest->pub.term_destination = JpegTermDestination;
	jpegDest->instance = this;
	jpegCinfo->dest = (struct jpeg_destination_mgr *)jpegDest;

	if (VNCZstdLevel > 0)
		vlog.info("ZSTD compression level set to %u", (int) VNCZstdLevel);
}

MPCompressor::~MPCompressor(void)
{
	if(setjmp(jpegErr->jmpBuffer)) {
		// this will execute if libjpeg has an error
		return;
	}
	jpeg_destroy_compress(jpegCinfo);
	delete jpegErr;
	delete jpegDest;
	delete jpegCinfo;

	if (zstdCctx)
		ZSTD_freeCCtx(zstdCctx);
	if (dataBuffer) {
		free(dataBuffer);
#if !defined(__APPLE__) && !defined(WIN32)
		vlog.debug("thread %lu: dataBufferLen %u", gettid(), dataBufferLen);
#endif
	}

#if !defined(__APPLE__) && !defined(WIN32)
	if (vlog.getLevel() >= 100) {
		unsigned compressionCountSum = 0;
		rdr::U64 totalBytesSum = 0;
		rdr::U64 totalCBytesSum = 0;
		int minCompressedTotal = 100000;
		int maxCompressedTotal = 0;
		int optimizationCountSum = 0;
		for (int i = MP_COMPRESSION_MIN; i <= MP_COMPRESSION_MAX; i++) {
			if (compressionCount[i] > 0 && totalBytes[i] > 0) {
				compressionCountSum += compressionCount[i];
				vlog.debug("thread %lu: compression %u %s: %u calls compressed %llu Bytes to %llu Bytes with ratio %llu%%, average rect size %llu uncompressed and %llu compressed, min/max compressed size %u/%u, optimizationCount %u", gettid(), i, mpCompressionName(i), compressionCount[i], totalBytes[i], totalCBytes[i], 100 * totalCBytes[i] / totalBytes[i], totalBytes[i]/compressionCount[i], totalCBytes[i]/compressionCount[i], minCompressed[i], maxCompressed[i], optimizationCount[i]);
				totalBytesSum += totalBytes[i];
				totalCBytesSum += totalCBytes[i];
				optimizationCountSum += optimizationCount[i];
				if (minCompressed[i] < minCompressedTotal)
					minCompressedTotal = minCompressed[i];
				if (maxCompressed[i] > maxCompressedTotal)
					maxCompressedTotal = maxCompressed[i];
			}
		}
		if (compressionCountSum > 0 && totalBytesSum > 0 && totalCBytesSum > 0)
			vlog.debug("thread %lu: %u calls compressed %llu Bytes to %llu Bytes with ratio %llu%%, average rect size %llu uncompressed and %llu compressed, min/max compressed size %u/%u, optimizationCount %u", gettid(), compressionCountSum, totalBytesSum, totalCBytesSum, 100 * totalCBytesSum / totalBytesSum, totalBytesSum/compressionCountSum, totalCBytesSum/compressionCountSum, minCompressedTotal, maxCompressedTotal, optimizationCountSum);
	}
#endif
	resetClassLog();
	resetClassMemLog();
}

static int i420DataSize(int mpWidth, int mpHeight)
{
	/* not perfect, but must stay compatible with old clients */
	return mpWidth * mpHeight * 3 / 2 + 2 * ((mpWidth & 1) * mpHeight + (mpHeight & 1) * mpWidth);
}

rdr::U8 * MPCompressor::getDataBuffer(const int newsize, const char * compName)
{
	if (unlikely(dataBufferLen < newsize)) {
		vlog.verbose2("getDataBuffer: resize dataBuffer for %s from %u to %u", compName, dataBufferLen, newsize);
		rdr::U8 * tmp = (rdr::U8*) realloc(dataBuffer, newsize);
		if (unlikely(!tmp)) {
			vlog.error("dataBuffer realloc(%u) for %s failed", newsize, compName);
			return NULL;
		}
		dataBuffer = tmp;
		dataBufferLen = newsize;
	}
	return dataBuffer;
}

#if !defined(WIN32)
bool MPCompressor::compress(const rdr::U8 *buf, int stride, const Rect& r,
	const PixelFormat& pf, int mpLevel, int mpCompression)
{
	const int w = r.width();
	const int h = r.height();
	const int rectSize = w * h;
	int mpWidth;
	int mpHeight;
//	const int mpCompressionWanted = mpCompression;

#if !defined(__APPLE__) && !defined(WIN32)
	ISVCEncoder* welsEncoder = NULL;
	x264_t *x264_h;
#endif

	const int srcPixelsize = 4;
	/* mpPixelsize may only be 3 or 4 */
	int mpPixelsize;
	int mpStride;
	int bufStride = stride * srcPixelsize;
	rdr::U8 * data;
	int ystride = 0;
	rdr::U8 * udata = NULL;
	int ustride = 0;
	rdr::U8 * vdata = NULL;
	int vstride = 0;
	int dataSize = 0;
	rdr::U8 * tmpBuf = NULL;
	const bool is888 = pf.is888();
	bool i420WExtra = false;
	bool i420HExtra = false;
	bool needI420 = false;
	bool needI420compat = false;
	bool needI444 = false;
	bool direct = false;
	bool wantOptSize = MPOptRectSize;

	if (!rectSize) {
		vlog.error("Zero rectSize");
		return false;
	}

	if (stride == 0)
		stride = w;

	if (unlikely(mpLevel < 0 || mpLevel > 9)) {
		vlog.error("invalid mpLevel %i, setting to 9", mpLevel);
		mpLevel = 9;
	}

	if (mpCompression == MP_COMPRESSION_AUTO) {
		if (saveBandwidthHigh) {
			if (rectSize < autoLbH264MinSizeK * 1000) {
				mpCompression = MP_COMPRESSION_JPEGLB;
			} else {
				int autoLbH264RateUsed = autoLbH264Rate;

				if (pressureLevel > 0) {
					if (autoLbH264RateUsed > pressureLevel)
						autoLbH264RateUsed -= pressureLevel;
					else
						autoLbH264RateUsed = 0;
				}
				if (autoLbH264RateUsed == 10) {
					mpCompression = MP_COMPRESSION_H264;
				} else if (autoLbH264RateUsed == 0) {
					mpCompression = MP_COMPRESSION_JPEGLB;
				} else {
					rdr::U8 randByte = 0;

					getrandom(&randByte, 1, 0);
					if ((int) randByte * 10 / 256 < autoLbH264RateUsed) {
						mpCompression = MP_COMPRESSION_H264;
					} else {
						mpCompression = MP_COMPRESSION_JPEGLB;
					}
				}
			}
		} else if (saveBandwidth) {
//			vlog.verbose("AUTO saveBandwidth: w: %u, h: %u, choosing JPEGLB", w, h);
			mpCompression = MP_COMPRESSION_JPEGLB;
		} else {
			if (pressureLevel == 0)
				mpCompression = MP_COMPRESSION_JPEG;
			else
				mpCompression = MP_COMPRESSION_JPEGLB;
		}
	}

	if (mpCompression == MP_COMPRESSION_H264) {
#if !defined(__APPLE__) && !defined(WIN32)
		if (clientSupportsTightMPX264) {
			if (rectSize < 500) {
				if (rectSize < 20)
					mpCompression = MP_COMPRESSION_RAWI444;
				else
					mpCompression = MP_COMPRESSION_I444ZSTD;
			} else {
				mpCompression = MP_COMPRESSION_H264_I444;
				wantOptSize = true;
			}
		} else {
#endif
			/* openh264 has min. size 16x16 and we increase to even numbers */
			if (h < 15 || w < 15) {
				mpCompression = MP_COMPRESSION_I420ZSTD;
			} else {
				wantOptSize = true;
			}
#if !defined(__APPLE__) && !defined(WIN32)
		}
#endif
	}
	if (mpCompression == MP_COMPRESSION_JPEGLB) {
		mpCompression = MP_COMPRESSION_JPEG;
		if (likely(mpLevel > 0))
			mpLevel--;
	}
	/* JPEG has min usedSize of 640B, try to save time and space (4B/pixel)
	 * do not waste resources for compression of small rects,
	 * result will be bigger anyway
	 */
	if (mpCompression == MP_COMPRESSION_JPEG) {
		if (mpLevel == 9) {
			if (rectSize <= 20)
				mpCompression = MP_COMPRESSION_RAW;
			else if (rectSize <= 200)
				mpCompression = MP_COMPRESSION_ZSTD;
			else
				wantOptSize = true;
		} else {
			if (rectSize <= 12)
				mpCompression = MP_COMPRESSION_RAWI420;
			else if (rectSize <= 400)
				mpCompression = MP_COMPRESSION_I420ZSTD;
			else
				wantOptSize = true;
		}
	}

	mpWidth = w;
	mpHeight = h;

	const size_t headerStartPos = length();
	/* write out: header of size MP_HEADER_SIZE */
	writeU8(mpCompression);
	/* flags: highest 4 Bits for mpLevel, lower 4 Bits currently unused */
	writeU8(mpLevel << 4);
	const size_t startPos = length();

	if (mpCompression == MP_COMPRESSION_RAW) {
		mpPixelsize = 4;
		mpStride = mpWidth * mpPixelsize;
		dataSize = mpHeight * mpStride;
		if (likely(is888)) {
//			vlog.verbose("RAW: directly use OutStream buffer with size %u", dataSize);
			direct = true;
			data = getptr(dataSize);
		} else {
			data = getDataBuffer(dataSize, "RAW");
			if (unlikely(!data))
				return false;
		}
	} else if (mpCompression == MP_COMPRESSION_JPEG) {
		const int quality = jpegConf[mpLevel].quality;

#ifdef JCS_EXTENSIONS
		mpPixelsize = 4;
#else
		mpPixelsize = 3;
#endif
		mpStride = mpWidth * mpPixelsize;
//		vlog.verbose("JPEG: w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u", w, h, stride, mpWidth, mpStride);
		dataSize = mpHeight * mpStride;
		if(unlikely(setjmp(jpegErr->jmpBuffer))) {
			// this will execute if libjpeg has an error
			jpeg_abort_compress(jpegCinfo);
			throw rdr::Exception("%s", jpegErr->lastError);
		}
		jpegCinfo->image_width = mpWidth;
		jpegCinfo->image_height = mpHeight;
#ifdef JCS_EXTENSIONS
		jpegCinfo->in_color_space = JCS_EXT_RGBX;
#else
		jpegCinfo->in_color_space = JCS_RGB;
#endif
		jpegCinfo->input_components = mpPixelsize;
		jpeg_set_defaults(jpegCinfo);
		jpeg_set_quality(jpegCinfo, quality, TRUE);
		if (quality >= 96)
			jpegCinfo->dct_method = JDCT_ISLOW;
		else
			jpegCinfo->dct_method = JDCT_FASTEST;
		if (quality >= 79) {
			jpegCinfo->comp_info[0].h_samp_factor = 1;
			jpegCinfo->comp_info[0].v_samp_factor = 1;
		} else if (quality <= 42) {
			jpegCinfo->comp_info[0].h_samp_factor = 2;
			jpegCinfo->comp_info[0].v_samp_factor = 2;
		} else {
			jpegCinfo->comp_info[0].h_samp_factor = 2;
			jpegCinfo->comp_info[0].v_samp_factor = 1;
		}
#ifdef JCS_EXTENSIONS
		if (likely(is888)) {
			JSAMPROW *jpegRowPointer;

			mpStride = bufStride;
//			vlog.verbose2("JPEG direct: w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u", w, h, stride, mpWidth, mpStride);
			jpegRowPointer = new JSAMPROW[mpHeight];
#pragma GCC ivdep
			for (int y = 0; y < mpHeight; y++)
				jpegRowPointer[y] = (JSAMPROW) buf + y * mpStride;
			jpeg_start_compress(jpegCinfo, TRUE);
			while (jpegCinfo->next_scanline < jpegCinfo->image_height)
				jpeg_write_scanlines(jpegCinfo, &jpegRowPointer[jpegCinfo->next_scanline],
					jpegCinfo->image_height - jpegCinfo->next_scanline);
			jpeg_finish_compress(jpegCinfo);
			delete[] jpegRowPointer;
			ssize_t usedSize = length() - startPos;
			if (wantOptSize && mpLevel < 9) {
				const int i420Size = i420DataSize(w, h) / 2;

				if (usedSize > i420Size + MP_COMPRESSION_MIN_SAVE_SIZE) {
					vlog.verbose2("wantOptSize: mpCompression: %s, mpLevel %u, w: %u, h: %u, stride: %u, mpWidth: %u, usedSize %lu > i420Size %u + %u, repeat with I420ZSTD", mpCompressionName(mpCompression), mpLevel, w, h, stride, mpWidth, usedSize, i420Size, MP_COMPRESSION_MIN_SAVE_SIZE);
					reposition(headerStartPos);
					optimizationCount[MP_COMPRESSION_I420ZSTD]++;
					return compress(buf, stride, r, pf, mpLevel, MP_COMPRESSION_I420ZSTD);
				}
			} else {
				const int zstdEstSize = rectSize * 2;

				if (usedSize > zstdEstSize + MP_COMPRESSION_MIN_SAVE_SIZE) {
					vlog.verbose2("optimize: mpCompression: %s, mpLevel: %u, w: %u, h: %u, stride: %u, mpWidth: %u, usedSize %lu > zstdEstSize %u + %u, repeat with ZSTD", mpCompressionName(mpCompression), mpLevel, w, h, stride, mpWidth, usedSize, zstdEstSize, MP_COMPRESSION_MIN_SAVE_SIZE);
					reposition(headerStartPos);
					optimizationCount[MP_COMPRESSION_ZSTD]++;
					return compress(buf, stride, r, pf, mpLevel, MP_COMPRESSION_ZSTD);
				}
			}
			if (vlog.getLevel() >= 100) {
				compressionCount[mpCompression]++;
				totalBytes[mpCompression] += mpWidth * mpHeight * srcPixelsize;
				totalCBytes[mpCompression] += usedSize;
				if (unlikely(usedSize < minCompressed[mpCompression]))
					minCompressed[mpCompression] = usedSize;
				if (unlikely(usedSize > maxCompressed[mpCompression]))
					maxCompressed[mpCompression] = usedSize;
			}
			return true;
		}
#endif
//		vlog.verbose2("JPEG indirect: w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u", w, h, stride, mpWidth, mpStride);
		data = getDataBuffer(dataSize, "JPEG");
		if (unlikely(!data))
			return false;
#if !defined(__APPLE__) && !defined(WIN32)
	} else if (mpCompression == MP_COMPRESSION_H264_I444 || mpCompression == MP_COMPRESSION_H264_I420) {
		if (mpCompression == MP_COMPRESSION_H264_I420) {
			/* x264 with I420 only supports even numbers for width and height */
			if (mpWidth & 1) {
				mpWidth++;
				i420WExtra = true;
			}
			if (mpHeight & 1) {
				mpHeight++;
				i420HExtra = true;
			}
		}
		mpPixelsize = 3;
		mpStride = mpWidth * mpPixelsize;
		x264_param.i_width  = mpWidth;
		x264_param.i_height = mpHeight;
		/* Constant Rate Factor, recommended values are 18 (good) to 28 (less good) */
		x264_param.rc.f_rf_constant = x264CRF + (9 - mpLevel);

		x264_h = x264_encoder_open(&x264_param);
		if(unlikely(!x264_h)) {
			vlog.error("x264_encoder_open() for H264_I4XX failed, w: %u, h: %u, stride: %u, mpWidth: %u", w, h, stride, mpWidth);
			return false;
		}
		if (mpCompression == MP_COMPRESSION_H264_I444) {
			x264_param.i_csp = X264_CSP_I444;
			needI444 = true;
		} else {
			x264_param.i_csp = X264_CSP_I420;
			needI420 = true;
		}

		if (x264_picture_alloc(&x264_pic, x264_param.i_csp, x264_param.i_width, x264_param.i_height ) < 0) {
			vlog.error("x264_picture_alloc() for H264_I4XX failed");
			x264_encoder_close(x264_h);
			return false;
		}
		data = x264_pic.img.plane[0];
		ystride = x264_pic.img.i_stride[0];
		udata = x264_pic.img.plane[1];
		ustride = x264_pic.img.i_stride[1];
		vdata = x264_pic.img.plane[2];
		vstride = x264_pic.img.i_stride[2];
	} else if (mpCompression == MP_COMPRESSION_H264) {
		int rv;

		/* openh264 only supports even numbers for width and height */
		if (mpWidth & 1) {
			mpWidth++;
			i420WExtra = true;
		}
		if (mpHeight & 1) {
			mpHeight++;
			i420HExtra = true;
		}
		/* mpLevel setting LOW, MEDIUM, HIGH, better is slower */
		if (mpLevel >= 7)
			welsComplexity = HIGH_COMPLEXITY;
		else if (unlikely(mpLevel <= 3))
			welsComplexity = LOW_COMPLEXITY;
		else
			welsComplexity = MEDIUM_COMPLEXITY;
		mpPixelsize = 3;
		mpStride = mpWidth * mpPixelsize;
		needI420 = true;
		needI420compat = true;
		dataSize = i420DataSize(mpWidth, mpHeight);
		data = getDataBuffer(dataSize, "H264");
		if (unlikely(!data))
			return false;
		rv = WelsCreateSVCEncoder(&welsEncoder);
		if (unlikely(rv != cmResultSuccess || welsEncoder == NULL)) {
			vlog.error("WelsCreateSVCEncoder() failed with error %i", rv);
			return false;
		}
		welsParam.iPicWidth = mpWidth;
		welsParam.iPicHeight = mpHeight;
		rv = welsEncoder->Initialize(&welsParam);
		if (unlikely(rv != cmResultSuccess)) {
			vlog.error("welsEncoder->Initialize() failed with error %i", rv);
			WelsDestroySVCEncoder(welsEncoder);
			return false;
		}
		rv = welsEncoder->SetOption(ENCODER_OPTION_DATAFORMAT, &welsVideoFormat);
		if (unlikely(rv != cmResultSuccess)) {
			vlog.error("elsEncoder->SetOption(ENCODER_OPTION_DATAFORMAT) failed with error %i", rv);
			welsEncoder->Uninitialize();
			WelsDestroySVCEncoder(welsEncoder);
			return false;
		}
		rv = welsEncoder->SetOption(ENCODER_OPTION_COMPLEXITY, &welsComplexity);
		if (unlikely(rv != cmResultSuccess)) {
			vlog.error("elsEncoder->SetOption(ENCODER_OPTION_COMPLEXITY) failed with error %i", rv);
			welsEncoder->Uninitialize();
			WelsDestroySVCEncoder(welsEncoder);
			return false;
		}
		rv = welsEncoder->SetOption(ENCODER_OPTION_SVC_ENCODE_PARAM_BASE, &welsParam);
		if (unlikely(rv != cmResultSuccess)) {
			vlog.error("elsEncoder->SetOption(ENCODER_OPTION_SVC_ENCODE_PARAM_BASE) failed with error %i", rv);
			welsEncoder->Uninitialize();
			WelsDestroySVCEncoder(welsEncoder);
			return false;
		}
#endif
	} else if (mpCompression == MP_COMPRESSION_RAWI420) {
		needI420 = true;
		mpPixelsize = 3;
		mpStride = mpWidth * mpPixelsize;
//		vlog.verbose("RAWI420: w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u", w, h, stride, mpWidth, mpStride);
		/* U and V each need 1/4 of pixels */
		dataSize = i420DataSize(mpWidth, mpHeight);
		needI420compat = true;
		if (likely(is888)) {
//			vlog.verbose("RAWI420: directly use OutStream buffer with size %u", dataSize);
			direct = true;
			data = getptr(dataSize);
		} else {
			data = getDataBuffer(dataSize, "RAWI420");
			if (unlikely(!data))
				return false;
		}
	} else if (mpCompression == MP_COMPRESSION_RAWI444) {
		needI444 = true;
		mpPixelsize = 3;
		mpStride = mpWidth * mpPixelsize;
		ustride = mpWidth;
		vstride = mpWidth;
//		vlog.verbose("RAWI444: w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u", w, h, stride, mpWidth, mpStride);
		dataSize = mpStride * mpHeight;
		if (likely(is888)) {
//			vlog.verbose("RAWI444: directly use OutStream buffer with size %u", dataSize);
			direct = true;
			data = getptr(dataSize);
		} else {
			data = getDataBuffer(dataSize, "RAWI444");
			if (unlikely(!data))
				return false;
		}
		udata = data + mpWidth * mpHeight;
		vdata = udata + ustride * mpHeight;
	} else if (mpCompression == MP_COMPRESSION_ZSTD) {
		mpPixelsize = 4;
		mpStride = mpWidth * mpPixelsize;
		dataSize = mpHeight * mpStride;
		if (unlikely(!zstdCctx)) {
			zstdCctx = ZSTD_createCCtx();
			if (unlikely(!zstdCctx)) {
				vlog.error("ZSTD_createCCtx() failed");
				return false;
			}
		}
		data = getDataBuffer(dataSize, "ZSTD");
		if (unlikely(!data))
			return false;
	} else if (mpCompression == MP_COMPRESSION_I420ZSTD || mpCompression == MP_COMPRESSION_I444ZSTD) {
		wantOptSize = true;
		mpPixelsize = 3;
		mpStride = mpWidth * mpPixelsize;
//		vlog.verbose("I420ZSTD/I444ZSTD: w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u", w, h, stride, mpWidth, mpStride);
		if (unlikely(!zstdCctx)) {
			zstdCctx = ZSTD_createCCtx();
			if (unlikely(!zstdCctx)) {
				vlog.error("ZSTD_createCCtx() failed");
				return false;
			}
		}
		if (mpCompression == MP_COMPRESSION_I420ZSTD) {
			needI420 = true;
			needI420compat = true;
			dataSize = i420DataSize(mpWidth, mpHeight);
		} else { /* MP_COMPRESSION_I444ZSTD */
			needI444 = true;
			ustride = mpWidth;
			vstride = mpWidth;
			dataSize = mpStride * mpHeight;
		}
		data = getDataBuffer(dataSize, "I420ZSTD/I444ZSTD");
		if (unlikely(!data))
			return false;
	} else {
		vlog.error("invalid MP compression %u", mpCompression);
		return false;
	}
	if (unlikely(!is888)) {
		vlog.verbose("not is888, use tmpBuf: w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u", w, h, stride, mpWidth, mpStride);
		tmpBuf = (rdr::U8*) malloc(rectSize * srcPixelsize);
		if (unlikely(!tmpBuf)) {
			vlog.error("malloc for tmpBuf failed");
#if !defined(__APPLE__) && !defined(WIN32)
			if (mpCompression == MP_COMPRESSION_H264) {
				welsEncoder->Uninitialize();
				WelsDestroySVCEncoder(welsEncoder);
			} else if (mpCompression == MP_COMPRESSION_H264_I444 || mpCompression == MP_COMPRESSION_H264_I420) {
				x264_picture_clean(&x264_pic);
				x264_encoder_close(x264_h);
			}
#endif
			return false;
		}
		pf.bufferFromBuffer(tmpBuf, pf, (const rdr::U8 *)buf, w, h, w, stride);
		buf = tmpBuf;
		stride = w;
		bufStride = stride * srcPixelsize;
	}

	/* stride is number of pixels per full source image line,
	 * bufStride is number of bytes per source image line
	 *   (usually bufStride = stride * srcPixelsize)
	 * mpStride is number of bytes per line
	 * both can be more than what is used.
	 */
	if (needI420) {
		const int yuvHeight = i420HExtra ? mpHeight - 1 : mpHeight;
		const int yuvWidth = i420WExtra ? mpWidth - 1 : mpWidth;

//		vlog.verbose("wanted compression: %u, selected compression: %u, needI420, mpLevel: %u, w: %u, h: %u, stride: %u, mpStride: %u", mpCompressionWanted, mpCompression, mpLevel, w, h, stride, mpStride);
		if (!ystride)
			ystride = mpWidth;
		if (!udata)
			udata = data + ystride * mpHeight;
		if (!ustride)
			ustride = ((mpWidth + 1) / 2);
		if (!vstride)
			vstride = ((mpWidth + 1) / 2);
		if (needI420compat) {
			if (!vdata)
				vdata = data + mpWidth * mpHeight + mpWidth * mpHeight / 4 + (mpWidth & 1) * mpHeight + (mpHeight & 1) * mpWidth;
			libyuv::ABGRToI420(buf, bufStride, data, mpWidth, udata, ustride, vdata, vstride, yuvWidth, yuvHeight);
		} else {
			if (!vdata)
				vdata = data + ystride * mpHeight + ustride * ((mpHeight + 1) / 2);
			libyuv::ABGRToI420(buf, bufStride, data, ystride, udata, ustride, vdata, vstride, yuvWidth, yuvHeight);
		}
		if (i420WExtra) {
			int y;
#pragma GCC ivdep
			for (y = 0; y < yuvHeight ; y++) {
				data[ystride * y + yuvWidth] = data[ystride * y + yuvWidth - 1];
			}
#pragma GCC ivdep
			for (y = 0; y < yuvHeight / 2 ; y++) {
				udata[ustride * (y + 1) - 1] = udata[ustride * (y + 1) - 2];
				vdata[vstride * (y + 1) - 1] = vdata[vstride * (y + 1) - 2];
			}
		}
		if (i420HExtra)
			memcpy(data + ystride * yuvHeight, data + ystride * (yuvHeight - 1), ystride);

	} else if (needI444) {
//		vlog.verbose("wanted compression: %u, selected compression: %u, needI444, mpLevel: %u, w: %u, h: %u, stride: %u, mpStride: %u", mpCompressionWanted, mpCompression, mpLevel, w, h, stride, mpStride);
		if (!ystride)
			ystride = mpWidth;
		if (!udata)
			udata = data + ystride * mpHeight;
		if (!ustride)
			ustride = mpWidth;
		if (!vdata)
			vdata = data + ystride * mpHeight + ustride * mpHeight;
		if (!vstride)
			vstride = mpWidth;
		rgbaToI444(buf, bufStride, data, ystride, udata, ustride, vdata, vstride, mpWidth, mpHeight);

	} else { /* !needI4xx */
		if(stride != w || w != mpWidth || mpStride != w * srcPixelsize) {
			const int srcoffset = bufStride;
			const int targetoffset = mpStride;
			int x;
			int y;

//			vlog.verbose("wanted compression: %u, selected compression: %u, mpLevel: %u, w: %u, h: %u, stride: %u, mpStride: %u", mpCompressionWanted, mpCompression, mpLevel, w, h, stride, mpStride);
			if (mpPixelsize == srcPixelsize) {
				const int linesize = w * srcPixelsize;

#pragma GCC ivdep
				for (y = 0; y < h ; y++)
					memcpy(data + y * targetoffset, buf + y * srcoffset, linesize);
			} else {
#pragma GCC ivdep
				for (y = 0; y < h ; y++) {
#pragma GCC ivdep
					for (x = 0; x < w ; x++)
						memcpy(data + targetoffset * y + mpPixelsize * x, buf + srcoffset * y + srcPixelsize * x, mpPixelsize);
				}
			}
		} else {
			if (mpCompression == MP_COMPRESSION_RAW) {
//				vlog.verbose("direct use: wanted compression: %u, selected compression: %u, mpLevel: %u, w: %u, h: %u, stride: %u, mpStride: %u", mpCompressionWanted, mpCompression, mpLevel, w, h, stride, mpStride);
				if (likely(direct)) {
					memcpy(data, buf, dataSize);
				} else {
					data = (rdr::U8*) buf;
				}
			} else {
//				vlog.verbose("direct copy: wanted compression: %u, selected compression: %u, mpLevel: %u, w: %u, h: %u, stride: %u, mpStride: %u", mpCompressionWanted, mpCompression, mpLevel, w, h, stride, mpStride);
				memcpy(data, buf, rectSize * srcPixelsize);
			}
		}
	}

	/* write out: body */
	if (mpCompression == MP_COMPRESSION_RAW) {
		if (likely(direct)) {
			reposition(length() + dataSize);
		} else {
			writeBytes(data, dataSize);
		}
	} else if (mpCompression == MP_COMPRESSION_JPEG) {
		JSAMPROW *jpegRowPointer;

		jpegRowPointer = new JSAMPROW[mpHeight];
#pragma GCC ivdep
		for (int y = 0; y < mpHeight; y++)
			jpegRowPointer[y] = (JSAMPROW) data + y * mpStride;
		jpeg_start_compress(jpegCinfo, TRUE);
		while (jpegCinfo->next_scanline < jpegCinfo->image_height)
			jpeg_write_scanlines(jpegCinfo, &jpegRowPointer[jpegCinfo->next_scanline],
				jpegCinfo->image_height - jpegCinfo->next_scanline);
		jpeg_finish_compress(jpegCinfo);
		delete[] jpegRowPointer;
#if !defined(__APPLE__) && !defined(WIN32)
	} else if (mpCompression == MP_COMPRESSION_H264_I444 || mpCompression == MP_COMPRESSION_H264_I420) {
		int x264_i_frame_size;
		x264_nal_t *x264_nal;
		int x264_i_nal;

//		vlog.verbose2("H264_I4XX: w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u, dataSize: %u", w, h, stride, mpWidth, mpStride, dataSize);

		x264_pic.i_pts = 0;
		x264_i_frame_size = x264_encoder_encode(x264_h, &x264_nal, &x264_i_nal, &x264_pic, &x264_pic_out);
		if (tmpBuf)
			free(tmpBuf);
		if (unlikely(x264_i_frame_size < 0)) {
			vlog.error("x264_encoder_encode(x264_pic) for H264_I4XX failed with error %i", x264_i_frame_size);
			x264_encoder_close(x264_h);
			x264_picture_clean(&x264_pic);
			return false;
		}
		if (x264_i_frame_size > 0) {
			writeBytes(x264_nal->p_payload, x264_i_frame_size);
//			vlog.verbose2("H264_I4XX: added %u bytes of output, length is %lu", x264_i_frame_size, length());
		}
		while(x264_encoder_delayed_frames(x264_h)) {
			x264_i_frame_size = x264_encoder_encode(x264_h, &x264_nal, &x264_i_nal, NULL, &x264_pic_out);
			if (unlikely(x264_i_frame_size < 0)) {
				vlog.error("x264_encoder_encode(NULL) for H264_I4XX failed with error %i", x264_i_frame_size);
				x264_encoder_close(x264_h);
				x264_picture_clean(&x264_pic);
				return false;
			}
			if (x264_i_frame_size > 0) {
				writeBytes(x264_nal->p_payload, x264_i_frame_size);
//				vlog.verbose2("H264_I4XX: added %u bytes of delayed output, length is %lu, x264_i_nal is %u", x264_i_frame_size, length(), x264_i_nal);
			}
		}
		x264_encoder_close(x264_h);
		x264_picture_clean(&x264_pic);
	} else if (mpCompression == MP_COMPRESSION_H264) {
		SFrameBSInfo info;
		int rv;
		int i;
		int j;
		SLayerBSInfo* pLayer;
		int iCurLayerBits;

//		vlog.verbose2("H264: w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u, dataSize: %u", w, h, stride, mpWidth, mpStride, dataSize);

		/* prepare pic: 12 Bit / pixel: I420: 1(Y) + 1/4(U) + 1/4(V) */
		memset (&welsPic, 0, sizeof(welsPic));
		welsPic.iColorFormat = videoFormatI420;
		welsPic.pData[0] = data;
		welsPic.pData[1] = welsPic.pData[0] + mpWidth * mpHeight;
		welsPic.pData[2] = welsPic.pData[1] + mpWidth * mpHeight / 4 + (mpWidth & 1) * mpHeight + (mpHeight & 1) * mpWidth;
		welsPic.iPicWidth = mpWidth;
		welsPic.iPicHeight = mpHeight;
		welsPic.iStride[0] = welsPic.iPicWidth;
		welsPic.iStride[1] = welsPic.iStride[2] = welsPic.iPicWidth >> 1;

		/* encode */
		memset (&info, 0, sizeof(info));
		rv = welsEncoder->EncodeFrame(&welsPic, &info);
		if (likely(rv == cmResultSuccess && info.eFrameType != videoFrameTypeInvalid && info.eFrameType != videoFrameTypeSkip && info.iLayerNum > 0)) {
			for (i = 0; i < info.iLayerNum; i++) {
				pLayer = &info.sLayerInfo[i];
				iCurLayerBits = 0;
				for (j = 0; j < pLayer->iNalCount; j++) {
					iCurLayerBits += pLayer->pNalLengthInByte[j];
				}
				writeBytes(pLayer->pBsBuf, iCurLayerBits);
			}
		} else {
			vlog.error("EncodeFrame() failed with rv %i, info.eFrameType %i, info.iLayerNum %i", rv, info.eFrameType, info.iLayerNum);
			welsEncoder->Uninitialize();
			WelsDestroySVCEncoder(welsEncoder);
			if (tmpBuf)
				free(tmpBuf);
			return false;
		}
		welsEncoder->Uninitialize();
		WelsDestroySVCEncoder(welsEncoder);
#endif
	} else if (mpCompression == MP_COMPRESSION_RAWI420 || mpCompression == MP_COMPRESSION_RAWI444) {
		if (likely(direct)) {
			reposition(length() + dataSize);
		} else {
			writeBytes(data, dataSize);
		}
	} else if (mpCompression == MP_COMPRESSION_ZSTD) {
		const int compressedDataSize = ZSTD_compressBound(dataSize);
		int compressedSize;

		compressedSize = ZSTD_compressCCtx(zstdCctx, getptr(compressedDataSize), compressedDataSize, data, dataSize, VNCZstdLevel);
		if (likely(compressedSize > 0)) {
			reposition(length() + compressedSize);
		} else {
			vlog.error("ZSTD: ZSTD_compressCCtx() failed with error %i", compressedSize);
			if (tmpBuf)
				free(tmpBuf);
			return false;
		}
	} else if (mpCompression == MP_COMPRESSION_I420ZSTD || mpCompression == MP_COMPRESSION_I444ZSTD) {
		const int compressedDataSize = ZSTD_compressBound(dataSize);
		int compressedSize;

		compressedSize = ZSTD_compressCCtx(zstdCctx, getptr(compressedDataSize), compressedDataSize, data, dataSize, VNCZstdLevel);
		if (likely(compressedSize > 0)) {
			reposition(length() + compressedSize);
		} else {
			vlog.error("I420ZSTD/I444ZSTD: ZSTD_compressCctx() failed with error %i", compressedSize);
			if (tmpBuf)
				free(tmpBuf);
			return false;
		}
	}
	if (tmpBuf)
		free(tmpBuf);

	/* size check */
	const int usedSize = length() - startPos;
	if (   mpCompression != MP_COMPRESSION_RAWI420
	    && mpCompression != MP_COMPRESSION_RAWI444
	    && mpCompression != MP_COMPRESSION_RAW
	    && usedSize >= MP_COMPRESSION_MIN_OPT_SIZE) {
		if (wantOptSize && mpCompression != MP_COMPRESSION_I420ZSTD && mpCompression != MP_COMPRESSION_I444ZSTD) {
#if !defined(__APPLE__) && !defined(WIN32)
			if (clientSupportsTightMPX264) {
				if (usedSize > rectSize + MP_COMPRESSION_MIN_SAVE_SIZE) {
					vlog.verbose2("wantOptSize: mpCompression: %s, w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u, usedSize %u > rectSize %u + %u, repeat with I444ZSTD", mpCompressionName(mpCompression), w, h, stride, mpWidth, mpStride, usedSize, rectSize, MP_COMPRESSION_MIN_SAVE_SIZE);
					reposition(headerStartPos);
					optimizationCount[MP_COMPRESSION_I444ZSTD]++;
					return compress(buf, stride, r, pf, mpLevel, MP_COMPRESSION_I444ZSTD);
				}
			} else {
#endif
				const int i420Size = i420DataSize(w, h) / 2;

				if (usedSize > i420Size + MP_COMPRESSION_MIN_SAVE_SIZE) {
					vlog.verbose2("wantOptSize: mpCompression: %s, w: %u, h: %u, stride: %u, mpWidth: %u, mpStride: %u, usedSize %u > i420Size %u + %u, repeat with I420ZSTD", mpCompressionName(mpCompression), w, h, stride, mpWidth, mpStride, usedSize, i420Size, MP_COMPRESSION_MIN_SAVE_SIZE);
					reposition(headerStartPos);
					optimizationCount[MP_COMPRESSION_I420ZSTD]++;
					return compress(buf, stride, r, pf, mpLevel, MP_COMPRESSION_I420ZSTD);
				}
#if !defined(__APPLE__) && !defined(WIN32)
			}
#endif
		} else {
			const int rawSize = rectSize * 4;

			if (rawSize < 80 || mpCompression == MP_COMPRESSION_ZSTD || mpCompression == MP_COMPRESSION_I420ZSTD || mpCompression == MP_COMPRESSION_I444ZSTD) {
				if (usedSize > rawSize + MP_COMPRESSION_MIN_SAVE_SIZE) {
					vlog.verbose2("optimize: mpCompression: %s, mpLevel: %u, w: %u, h: %u, stride: %u, mpWidth: %u, usedSize %u > rawSize %u + %u, repeat with RAW", mpCompressionName(mpCompression), mpLevel, w, h, stride, mpWidth, usedSize, rawSize, MP_COMPRESSION_MIN_SAVE_SIZE);
					reposition(headerStartPos);
					optimizationCount[MP_COMPRESSION_RAW]++;
					return compress(buf, stride, r, pf, mpLevel, MP_COMPRESSION_RAW);
				}
			} else {
				if (usedSize > rawSize / 2 + MP_COMPRESSION_MIN_SAVE_SIZE) {
					vlog.verbose2("optimize: mpCompression: %s, mpLevel: %u, w: %u, h: %u, stride: %u, mpWidth: %u, usedSize %u > zstdEstSize %u + %u, repeat with ZSTD", mpCompressionName(mpCompression), mpLevel, w, h, stride, mpWidth, usedSize, rawSize / 2, MP_COMPRESSION_MIN_SAVE_SIZE);
					reposition(headerStartPos);
					optimizationCount[MP_COMPRESSION_ZSTD]++;
					return compress(buf, stride, r, pf, mpLevel, MP_COMPRESSION_ZSTD);
				}
			}
		}
	}

	if (vlog.getLevel() >= 100) {
		compressionCount[mpCompression]++;
		totalBytes[mpCompression] += mpWidth * mpHeight * srcPixelsize;
		totalCBytes[mpCompression] += usedSize;
		if (usedSize < minCompressed[mpCompression])
			minCompressed[mpCompression] = usedSize;
		if (usedSize > maxCompressed[mpCompression])
			maxCompressed[mpCompression] = usedSize;
	}
	return true;
}
#endif /* #if !defined(WIN32) */
