1 /*
2  *  Copyright (C) 2002-2021  The DOSBox Team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License along
15  *  with this program; if not, write to the Free Software Foundation, Inc.,
16  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 //
19 // Zipped Motion Block Video
20 //
21 // Based on Huffyuv by Ben Rudiak-Gould.
22 // which was based on MSYUV sample code, which is:
23 // Copyright (c) 1993 Microsoft Corporation.
24 // All Rights Reserved.
25 //
26 
27 #include "zmbv_vfw.h"
28 #include "resource.h"
29 
30 #include <cstdint>
31 #include <crtdbg.h>
32 #include <string.h>
33 
34 TCHAR szDescription[] = TEXT("Zipped Motion Block Video v0.1a");
35 TCHAR szName[]        = TEXT(CODEC_4CC);
36 
37 #define VERSION         0x00000002      // newer version
38 
39 /********************************************************************
40 ********************************************************************/
41 
42 CodecInst *encode_table_owner, *decode_table_owner;
43 
44 /********************************************************************
45 ********************************************************************/
46 
Msg(const char fmt[],...)47 void Msg(const char fmt[], ...) {
48   DWORD written;
49   char buf[2000];
50   va_list val;
51 
52   va_start(val, fmt);
53   wvsprintf(buf, fmt, val);
54   va_end(val);
55 
56   const COORD _80x50 = {80,50};
57   static BOOL startup = (AllocConsole(), SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), _80x50));
58   WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), buf, lstrlen(buf), &written, 0);
59 }
60 
61 
62 /********************************************************************
63 ********************************************************************/
64 
CodecInst()65 CodecInst::CodecInst() {
66 	codec = 0;
67 }
68 
Open(ICOPEN * icinfo)69 CodecInst* Open(ICOPEN* icinfo) {
70   if (icinfo && icinfo->fccType != ICTYPE_VIDEO)
71       return NULL;
72 
73   CodecInst* pinst = new CodecInst();
74 
75   if (icinfo) icinfo->dwError = pinst ? ICERR_OK : ICERR_MEMORY;
76 
77   return pinst;
78 }
79 
Close(CodecInst * pinst)80 DWORD Close(CodecInst* pinst) {
81 //    delete pinst;       // this caused problems when deleting at app close time
82     return 1;
83 }
84 
85 /********************************************************************
86 ********************************************************************/
87 
88 
89 /********************************************************************
90 ********************************************************************/
91 
QueryAbout()92 BOOL CodecInst::QueryAbout() { return TRUE; }
93 
AboutDialogProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)94 static INT_PTR CALLBACK AboutDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
95   if (uMsg == WM_COMMAND) {
96     switch (LOWORD(wParam)) {
97     case IDOK:
98       EndDialog(hwndDlg, 0);
99       break;
100     case IDC_HOMEPAGE:
101       ShellExecute(NULL, NULL, "http://www.dosbox.com", NULL, NULL, SW_SHOW);
102       break;
103     case IDC_EMAIL:
104       ShellExecute(NULL, NULL, "mailto:dosbox.crew@gmail.com", NULL, NULL, SW_SHOW);
105       break;
106     }
107   }
108   return FALSE;
109 }
About(HWND hwnd)110 DWORD CodecInst::About(HWND hwnd) {
111   DialogBox(hmoduleCodec, MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDialogProc);
112   return ICERR_OK;
113 }
114 
ConfigureDialogProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)115 static INT_PTR CALLBACK ConfigureDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
116 
117   if (uMsg == WM_INITDIALOG) {
118 
119   } else if (uMsg == WM_COMMAND) {
120 
121     switch (LOWORD(wParam)) {
122 
123     case IDOK:
124 
125     case IDCANCEL:
126       EndDialog(hwndDlg, 0);
127       break;
128 
129     default:
130       return AboutDialogProc(hwndDlg, uMsg, wParam, lParam);    // handle email and home-page buttons
131     }
132   }
133   return FALSE;
134 }
135 
QueryConfigure()136 BOOL CodecInst::QueryConfigure() { return TRUE; }
137 
Configure(HWND hwnd)138 DWORD CodecInst::Configure(HWND hwnd) {
139   DialogBox(hmoduleCodec, MAKEINTRESOURCE(IDD_CONFIGURE), hwnd, ConfigureDialogProc);
140   return ICERR_OK;
141 }
142 
143 
144 /********************************************************************
145 ********************************************************************/
146 
147 
148 // we have no state information which needs to be stored
149 
GetState(LPVOID pv,DWORD dwSize)150 DWORD CodecInst::GetState(LPVOID pv, DWORD dwSize) { return 0; }
151 
SetState(LPVOID pv,DWORD dwSize)152 DWORD CodecInst::SetState(LPVOID pv, DWORD dwSize) { return 0; }
153 
154 
GetInfo(ICINFO * icinfo,DWORD dwSize)155 DWORD CodecInst::GetInfo(ICINFO* icinfo, DWORD dwSize) {
156   if (icinfo == NULL)
157     return sizeof(ICINFO);
158 
159   if (dwSize < sizeof(ICINFO))
160     return 0;
161 
162   icinfo->dwSize            = sizeof(ICINFO);
163   icinfo->fccType           = ICTYPE_VIDEO;
164   memcpy(&icinfo->fccHandler,CODEC_4CC, 4);
165   icinfo->dwFlags           = VIDCF_FASTTEMPORALC | VIDCF_FASTTEMPORALD | VIDCF_TEMPORAL;
166 
167   icinfo->dwVersion         = VERSION;
168   icinfo->dwVersionICM      = ICVERSION;
169   MultiByteToWideChar(CP_ACP, 0, szDescription, -1, icinfo->szDescription, sizeof(icinfo->szDescription)/sizeof(WCHAR));
170   MultiByteToWideChar(CP_ACP, 0, szName, -1, icinfo->szName, sizeof(icinfo->szName)/sizeof(WCHAR));
171 
172   return sizeof(ICINFO);
173 }
174 
175 /********************************************************************
176 ****************************************************************/
177 
GetInputBitDepth(const BITMAPINFOHEADER * lpbiIn)178 static int GetInputBitDepth(const BITMAPINFOHEADER *lpbiIn) {
179 	//Msg( "Get input depth compression %d bitcount %d\n", lpbiIn->biCompression, lpbiIn->biBitCount );
180 	if (lpbiIn->biCompression == BI_RGB) {
181 		if (lpbiIn->biPlanes != 1)
182 			return -1;
183 
184 		switch(lpbiIn->biBitCount) {
185 			case 8:
186 				return 8;
187 			case 16:
188 				return 15;		// Standard Windows 16-bit RGB is 1555.
189 			case 32:
190 				return 32;
191 		}
192 
193 	} else if (lpbiIn->biCompression == BI_BITFIELDS) {
194 		// BI_BITFIELDS RGB masks lie right after the BITMAPINFOHEADER structure,
195 		// at (ptr+40). This is true even for a BITMAPV4HEADER or BITMAPV5HEADER.
196 		const DWORD *masks = (const DWORD *)(lpbiIn + 1);
197 
198 		if (lpbiIn->biBitCount == 16) {
199 			// Test for 16 (555)
200 			if (masks[0] == 0x7C00 && masks[1] == 0x03E0 && masks[2] == 0x001F)
201 				return 15;
202 
203 			// Test for 16 (565)
204 			if (masks[0] == 0xF800 && masks[1] == 0x07E0 && masks[2] == 0x001F)
205 				return 16;
206 		} else if (lpbiIn->biBitCount == 32) {
207 			if (masks[0] == 0xFF0000 && masks[1] == 0x00FF00 && masks[2] == 0x0000FF)
208 				return 32;
209 		}
210 	}
211 
212 	return -1;
213 }
214 
CanCompress(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut,bool requireOutput)215 static bool CanCompress(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut, bool requireOutput) {
216 	if (lpbiIn) {
217 		if (GetInputBitDepth(lpbiIn) < 0)
218 			return false;
219 	} else
220 		return false;
221 	if (lpbiOut) {
222 		//Needs to match our 4cc format
223 		if (memcmp(&lpbiOut->biCompression,CODEC_4CC, 4))
224 			return false;
225 	} else
226 		return !requireOutput;
227 	return true;
228 }
229 
230 /********************************************************************
231 ****************************************************************/
232 
CompressQuery(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut)233 DWORD CodecInst::CompressQuery(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut) {
234 	if (CanCompress(lpbiIn,lpbiOut,false))
235 		return ICERR_OK;
236 	return ICERR_BADFORMAT;
237 }
238 
CompressGetFormat(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut)239 DWORD CodecInst::CompressGetFormat(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut) {
240 	if (!lpbiOut)
241 		return sizeof(BITMAPINFOHEADER);
242 	lpbiOut->biSize			= sizeof(BITMAPINFOHEADER);
243 	lpbiOut->biWidth		= lpbiIn->biWidth;
244 	lpbiOut->biHeight		= lpbiIn->biHeight;
245 	lpbiOut->biPlanes		= 1;
246 	lpbiOut->biCompression	= *(const DWORD *)CODEC_4CC;
247 	lpbiOut->biBitCount		= lpbiIn->biBitCount;
248 	lpbiOut->biSizeImage	= lpbiIn->biWidth * lpbiIn->biHeight * lpbiIn->biBitCount/8 + 1024;
249 	lpbiOut->biXPelsPerMeter = lpbiIn->biXPelsPerMeter;
250 	lpbiOut->biYPelsPerMeter = lpbiIn->biYPelsPerMeter;
251 	lpbiOut->biClrUsed		= 0;
252 	lpbiOut->biClrImportant	= 0;
253 	return ICERR_OK;
254 }
255 
CompressBegin(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut)256 DWORD CodecInst::CompressBegin(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut) {
257 	CompressEnd();  // free resources if necessary
258 	if (!CanCompress(lpbiIn, lpbiOut, true))
259 		return ICERR_BADFORMAT;
260 	codec = new VideoCodec();
261 	if (!codec)
262 		return ICERR_MEMORY;
263 	if (!codec->SetupCompress( lpbiIn->biWidth, lpbiIn->biHeight))
264 		return ICERR_MEMORY;
265 	return ICERR_OK;
266 }
267 
CompressGetSize(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut)268 DWORD CodecInst::CompressGetSize(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut) {
269 	if (!CanCompress(lpbiIn, lpbiOut, true))
270 		return ICERR_BADFORMAT;
271 	return lpbiIn->biWidth * lpbiIn->biHeight * lpbiIn->biBitCount/8 + 1024;
272 }
273 
Compress(ICCOMPRESS * icinfo,DWORD dwSize)274 DWORD CodecInst::Compress(ICCOMPRESS* icinfo, DWORD dwSize) {
275 	int i, pitch;
276 	ZMBV_FORMAT format;
277 	LPBITMAPINFOHEADER lpbiIn=icinfo->lpbiInput;
278 	LPBITMAPINFOHEADER lpbiOut=icinfo->lpbiOutput;
279 	if (!CanCompress(lpbiIn, lpbiOut, true))
280 		return ICERR_BADFORMAT;
281 	if (!icinfo->lpInput || !icinfo->lpOutput)
282 		return ICERR_ABORT;
283 	switch (GetInputBitDepth(lpbiIn)) {
284 	case 8:
285 		format = ZMBV_FORMAT::BPP_8;
286 		pitch = lpbiIn->biWidth;
287 		break;
288 	case 15:
289 		format = ZMBV_FORMAT::BPP_15;
290 		pitch = lpbiIn->biWidth * 2;
291 		break;
292 	case 16:
293 		format = ZMBV_FORMAT::BPP_16;
294 		pitch = lpbiIn->biWidth * 2;
295 		break;
296 	case 24:
297 	case 32:
298 		format = ZMBV_FORMAT::BPP_32;
299 		pitch = lpbiIn->biWidth * 4;
300 		break;
301 	}
302 
303 	// DIB scanlines for RGB formats are always aligned to DWORD.
304 	pitch = (pitch + 3) & ~3;
305 
306 	// force a key frame if requested by the client
307 	int flags = 0;
308 	if (icinfo->dwFlags & ICCOMPRESS_KEYFRAME)
309 		flags |= 1;
310 
311 	uint8_t palette[256 * 4];
312 	uint8_t *pal = NULL;
313 
314 	if (lpbiIn->biBitCount == 8) {
315 		const int entries = icinfo->lpbiInput->biClrUsed ? icinfo->lpbiInput->biClrUsed : 256;
316 		const char* srcpal = (char *)icinfo->lpbiInput + icinfo->lpbiInput->biSize;
317 		uint8_t * dstpal = palette;
318 
319 		memset(palette, 0, sizeof palette);
320 
321 		for(int i=0; i<entries; ++i) {
322 			dstpal[0] = srcpal[2];
323 			dstpal[1] = srcpal[1];
324 			dstpal[2] = srcpal[0];
325 			dstpal[3] = 0;
326 			dstpal += 4;
327 			srcpal += 4;
328 		}
329 
330 		pal = palette;
331 	}
332 
333 	codec->PrepareCompressFrame( flags, format, pal, reinterpret_cast<uint8_t*>(icinfo->lpOutput), 99999999);
334 	char *readPt = (char *)icinfo->lpInput + pitch*(lpbiIn->biHeight - 1);
335 	for(i = 0;i<lpbiIn->biHeight;i++) {
336 		codec->CompressLines(1, reinterpret_cast<uint8_t **>(&readPt));
337 		readPt -= pitch;
338 	}
339 	lpbiOut->biSizeImage = codec->FinishCompressFrame();
340 
341 	if (flags & 1)
342 		*icinfo->lpdwFlags = AVIIF_KEYFRAME;
343 	else
344 		*icinfo->lpdwFlags = 0;
345 
346 	return ICERR_OK;
347 }
348 
349 
CompressEnd()350 DWORD CodecInst::CompressEnd() {
351 	if (codec)
352 		delete codec;
353 	codec = 0;
354 	return ICERR_OK;
355 }
356 
357 /********************************************************************
358 ********************************************************************/
359 
CanDecompress(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut)360 static bool CanDecompress(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut) {
361 	if (memcmp(&lpbiIn->biCompression,CODEC_4CC,4))
362 		return false;
363 	if (lpbiOut) {
364 		if (lpbiOut->biCompression!=0) return false;
365 		if (lpbiOut->biBitCount != 24) return false;
366 		if (lpbiIn->biWidth!=lpbiOut->biWidth || lpbiIn->biHeight!=lpbiOut->biHeight)
367             return false;
368 	}
369 	return true;
370 }
371 
372 /********************************************************************
373 ********************************************************************/
374 
375 
DecompressQuery(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut)376 DWORD CodecInst::DecompressQuery(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut) {
377 	return CanDecompress(lpbiIn, lpbiOut) ? ICERR_OK : ICERR_BADFORMAT;
378 }
379 
DecompressGetFormat(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut)380 DWORD CodecInst::DecompressGetFormat(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut) {
381 	if (memcmp(&lpbiIn->biCompression,CODEC_4CC,4))
382 		 return ICERR_BADFORMAT;
383 	if (!lpbiOut)
384 		return sizeof(BITMAPINFOHEADER);
385 	*lpbiOut = *lpbiIn;
386 	lpbiOut->biPlanes		= 1;
387 	lpbiOut->biSize			= sizeof(BITMAPINFOHEADER);
388 	lpbiOut->biBitCount		= 24;
389 	lpbiOut->biSizeImage	= ((lpbiOut->biWidth*3 + 3) & ~3) * lpbiOut->biHeight;
390 	lpbiOut->biCompression	= BI_RGB;
391 	lpbiOut->biClrUsed		= 0;
392 	lpbiOut->biClrImportant	= 0;
393 	return ICERR_OK;
394 }
395 
DecompressBegin(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut)396 DWORD CodecInst::DecompressBegin(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut) {
397 	DecompressEnd();  // free resources if necessary
398 	if (!CanDecompress(lpbiIn, lpbiOut))
399 		return ICERR_BADFORMAT;
400 	codec=new VideoCodec();
401 	if (!codec)
402 		return ICERR_MEMORY;
403 	if (!codec->SetupDecompress( lpbiIn->biWidth, lpbiIn->biHeight))
404 		return ICERR_MEMORY;
405 	return ICERR_OK;
406 }
407 
Decompress(ICDECOMPRESS * icinfo,DWORD dwSize)408 DWORD CodecInst::Decompress(ICDECOMPRESS* icinfo, DWORD dwSize) {
409 	if (!codec || !icinfo)
410 		return ICERR_ABORT;
411 	if (codec->DecompressFrame(reinterpret_cast<uint8_t*>(icinfo->lpInput), icinfo->lpbiInput->biSizeImage)) {
412 		codec->Output_UpsideDown_24(reinterpret_cast<uint8_t*>(icinfo->lpOutput));
413 	} else return ICERR_DONTDRAW;
414 	return ICERR_OK;
415 }
416 
417 
418 // palette-mapped output only
DecompressGetPalette(LPBITMAPINFOHEADER lpbiIn,LPBITMAPINFOHEADER lpbiOut)419 DWORD CodecInst::DecompressGetPalette(LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut) {
420 	return ICERR_BADFORMAT;
421 }
422 
DecompressEnd()423 DWORD CodecInst::DecompressEnd() {
424 	if (codec)
425 		delete codec;
426 	codec = 0;
427 	return ICERR_OK;
428 }
429