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