wintrust: Use helper function for setting confidence in SoftpubCheckCert.
[wine] / dlls / gdiplus / image.c
1 /*
2  * Copyright (C) 2007 Google (Evan Stade)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include <stdarg.h>
20
21 #include "windef.h"
22 #include "winbase.h"
23 #include "winuser.h"
24 #include "wingdi.h"
25
26 #define COBJMACROS
27 #include "objbase.h"
28 #include "olectl.h"
29 #include "ole2.h"
30
31 #include "gdiplus.h"
32 #include "gdiplus_private.h"
33 #include "wine/debug.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
36
37 #define PIXELFORMATBPP(x) ((x) ? ((x) >> 8) & 255 : 24)
38
39 static INT ipicture_pixel_height(IPicture *pic)
40 {
41     HDC hdcref;
42     OLE_YSIZE_HIMETRIC y;
43
44     IPicture_get_Height(pic, &y);
45
46     hdcref = GetDC(0);
47
48     y = MulDiv(y, GetDeviceCaps(hdcref, LOGPIXELSY), INCH_HIMETRIC);
49     ReleaseDC(0, hdcref);
50
51     return y;
52 }
53
54 static INT ipicture_pixel_width(IPicture *pic)
55 {
56     HDC hdcref;
57     OLE_XSIZE_HIMETRIC x;
58
59     IPicture_get_Width(pic, &x);
60
61     hdcref = GetDC(0);
62
63     x = MulDiv(x, GetDeviceCaps(hdcref, LOGPIXELSX), INCH_HIMETRIC);
64
65     ReleaseDC(0, hdcref);
66
67     return x;
68 }
69
70 GpStatus WINGDIPAPI GdipBitmapGetPixel(GpBitmap* bitmap, INT x, INT y,
71     ARGB *color)
72 {
73     static int calls;
74     TRACE("%p %d %d %p\n", bitmap, x, y, color);
75
76     if(!bitmap || !color)
77         return InvalidParameter;
78
79     if(!(calls++))
80         FIXME("not implemented\n");
81
82     *color = 0xdeadbeef;
83
84     return NotImplemented;
85 }
86
87 /* This function returns a pointer to an array of pixels that represents the
88  * bitmap. The *entire* bitmap is locked according to the lock mode specified by
89  * flags.  It is correct behavior that a user who calls this function with write
90  * privileges can write to the whole bitmap (not just the area in rect).
91  *
92  * FIXME: only used portion of format is bits per pixel. */
93 GpStatus WINGDIPAPI GdipBitmapLockBits(GpBitmap* bitmap, GDIPCONST GpRect* rect,
94     UINT flags, PixelFormat format, BitmapData* lockeddata)
95 {
96     BOOL bm_is_selected;
97     INT stride, bitspp = PIXELFORMATBPP(format);
98     OLE_HANDLE hbm;
99     HDC hdc;
100     HBITMAP old = NULL;
101     BITMAPINFO bmi;
102     BYTE *buff = NULL;
103     UINT abs_height;
104     GpRect act_rect; /* actual rect to be used */
105
106     TRACE("%p %p %d %d %p\n", bitmap, rect, flags, format, lockeddata);
107
108     if(!lockeddata || !bitmap)
109         return InvalidParameter;
110
111     if(rect){
112         if(rect->X < 0 || rect->Y < 0 || (rect->X + rect->Width > bitmap->width) ||
113           (rect->Y + rect->Height > bitmap->height) || !flags)
114             return InvalidParameter;
115
116         act_rect = *rect;
117     }
118     else{
119         act_rect.X = act_rect.Y = 0;
120         act_rect.Width  = bitmap->width;
121         act_rect.Height = bitmap->height;
122     }
123
124     if(flags & ImageLockModeUserInputBuf)
125         return NotImplemented;
126
127     if(bitmap->lockmode)
128         return WrongState;
129
130     IPicture_get_Handle(bitmap->image.picture, &hbm);
131     IPicture_get_CurDC(bitmap->image.picture, &hdc);
132     bm_is_selected = (hdc != 0);
133
134     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
135     bmi.bmiHeader.biBitCount = 0;
136
137     if(!bm_is_selected){
138         hdc = CreateCompatibleDC(0);
139         old = SelectObject(hdc, (HBITMAP)hbm);
140     }
141
142     /* fill out bmi */
143     GetDIBits(hdc, (HBITMAP)hbm, 0, 0, NULL, &bmi, DIB_RGB_COLORS);
144
145     abs_height = abs(bmi.bmiHeader.biHeight);
146     stride = bmi.bmiHeader.biWidth * bitspp / 8;
147     stride = (stride + 3) & ~3;
148
149     buff = GdipAlloc(stride * abs_height);
150
151     bmi.bmiHeader.biBitCount = bitspp;
152
153     if(buff)
154         GetDIBits(hdc, (HBITMAP)hbm, 0, abs_height, buff, &bmi, DIB_RGB_COLORS);
155
156     if(!bm_is_selected){
157         SelectObject(hdc, old);
158         DeleteDC(hdc);
159     }
160
161     if(!buff)
162         return OutOfMemory;
163
164     lockeddata->Width  = act_rect.Width;
165     lockeddata->Height = act_rect.Height;
166     lockeddata->PixelFormat = format;
167     lockeddata->Reserved = flags;
168
169     if(bmi.bmiHeader.biHeight > 0){
170         lockeddata->Stride = -stride;
171         lockeddata->Scan0  = buff + (bitspp / 8) * act_rect.X +
172                              stride * (abs_height - 1 - act_rect.Y);
173     }
174     else{
175         lockeddata->Stride = stride;
176         lockeddata->Scan0  = buff + (bitspp / 8) * act_rect.X + stride * act_rect.Y;
177     }
178
179     bitmap->lockmode = flags;
180     bitmap->numlocks++;
181
182     bitmap->bitmapbits = buff;
183
184     return Ok;
185 }
186
187 GpStatus WINGDIPAPI GdipBitmapUnlockBits(GpBitmap* bitmap,
188     BitmapData* lockeddata)
189 {
190     OLE_HANDLE hbm;
191     HDC hdc;
192     HBITMAP old = NULL;
193     BOOL bm_is_selected;
194     BITMAPINFO bmi;
195
196     if(!bitmap || !lockeddata)
197         return InvalidParameter;
198
199     if(!bitmap->lockmode)
200         return WrongState;
201
202     if(lockeddata->Reserved & ImageLockModeUserInputBuf)
203         return NotImplemented;
204
205     if(lockeddata->Reserved & ImageLockModeRead){
206         if(!(--bitmap->numlocks))
207             bitmap->lockmode = 0;
208
209         GdipFree(bitmap->bitmapbits);
210         bitmap->bitmapbits = NULL;
211         return Ok;
212     }
213
214     IPicture_get_Handle(bitmap->image.picture, &hbm);
215     IPicture_get_CurDC(bitmap->image.picture, &hdc);
216     bm_is_selected = (hdc != 0);
217
218     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
219     bmi.bmiHeader.biBitCount = 0;
220
221     if(!bm_is_selected){
222         hdc = CreateCompatibleDC(0);
223         old = SelectObject(hdc, (HBITMAP)hbm);
224     }
225
226     GetDIBits(hdc, (HBITMAP)hbm, 0, 0, NULL, &bmi, DIB_RGB_COLORS);
227     bmi.bmiHeader.biBitCount = PIXELFORMATBPP(lockeddata->PixelFormat);
228     SetDIBits(hdc, (HBITMAP)hbm, 0, abs(bmi.bmiHeader.biHeight),
229               bitmap->bitmapbits, &bmi, DIB_RGB_COLORS);
230
231     if(!bm_is_selected){
232         SelectObject(hdc, old);
233         DeleteDC(hdc);
234     }
235
236     GdipFree(bitmap->bitmapbits);
237     bitmap->bitmapbits = NULL;
238     bitmap->lockmode = 0;
239
240     return Ok;
241 }
242
243 GpStatus WINGDIPAPI GdipCloneImage(GpImage *image, GpImage **cloneImage)
244 {
245     IStream* stream;
246     HRESULT hr;
247     INT size;
248
249     TRACE("%p, %p\n", image, cloneImage);
250
251     if (!image || !cloneImage)
252         return InvalidParameter;
253
254     hr = CreateStreamOnHGlobal(0, TRUE, &stream);
255     if (FAILED(hr))
256         return GenericError;
257
258     *cloneImage = GdipAlloc(sizeof(GpImage));
259     if (!*cloneImage)
260     {
261         IStream_Release(stream);
262         return OutOfMemory;
263     }
264     (*cloneImage)->type = image->type;
265     (*cloneImage)->flags = image->flags;
266
267     hr = IPicture_SaveAsFile(image->picture, stream, FALSE, &size);
268     if(FAILED(hr))
269     {
270         WARN("Failed to save image on stream\n");
271         goto out;
272     }
273
274     hr = OleLoadPicture(stream, size, FALSE, &IID_IPicture,
275             (LPVOID*) &(*cloneImage)->picture);
276     if (FAILED(hr))
277     {
278         WARN("Failed to load image from stream\n");
279         goto out;
280     }
281
282     IStream_Release(stream);
283     return Ok;
284 out:
285     IStream_Release(stream);
286     GdipFree(*cloneImage);
287     *cloneImage = NULL;
288     return GenericError;
289 }
290
291 GpStatus WINGDIPAPI GdipCreateBitmapFromFile(GDIPCONST WCHAR* filename,
292     GpBitmap **bitmap)
293 {
294     GpStatus stat;
295     IStream *stream;
296
297     TRACE("(%s) %p\n", debugstr_w(filename), bitmap);
298
299     if(!filename || !bitmap)
300         return InvalidParameter;
301
302     stat = GdipCreateStreamOnFile(filename, GENERIC_READ, &stream);
303
304     if(stat != Ok)
305         return stat;
306
307     stat = GdipCreateBitmapFromStream(stream, bitmap);
308
309     IStream_Release(stream);
310
311     return stat;
312 }
313
314 GpStatus WINGDIPAPI GdipCreateBitmapFromGdiDib(GDIPCONST BITMAPINFO* info,
315                                                VOID *bits, GpBitmap **bitmap)
316 {
317     DWORD height, stride;
318     PixelFormat format;
319
320     FIXME("(%p, %p, %p) - partially implemented\n", info, bits, bitmap);
321
322     height = abs(info->bmiHeader.biHeight);
323     stride = ((info->bmiHeader.biWidth * info->bmiHeader.biBitCount + 31) >> 3) & ~3;
324
325     if(info->bmiHeader.biHeight > 0) /* bottom-up */
326     {
327         bits = (BYTE*)bits + (height - 1) * stride;
328         stride = -stride;
329     }
330
331     switch(info->bmiHeader.biBitCount) {
332     case 1:
333         format = PixelFormat1bppIndexed;
334         break;
335     case 4:
336         format = PixelFormat4bppIndexed;
337         break;
338     case 8:
339         format = PixelFormat8bppIndexed;
340         break;
341     case 24:
342         format = PixelFormat24bppRGB;
343         break;
344     default:
345         FIXME("don't know how to handle %d bpp\n", info->bmiHeader.biBitCount);
346         *bitmap = NULL;
347         return InvalidParameter;
348     }
349
350     return GdipCreateBitmapFromScan0(info->bmiHeader.biWidth, height, stride, format,
351                                      bits, bitmap);
352
353 }
354
355 /* FIXME: no icm */
356 GpStatus WINGDIPAPI GdipCreateBitmapFromFileICM(GDIPCONST WCHAR* filename,
357     GpBitmap **bitmap)
358 {
359     TRACE("(%s) %p\n", debugstr_w(filename), bitmap);
360
361     return GdipCreateBitmapFromFile(filename, bitmap);
362 }
363
364 GpStatus WINGDIPAPI GdipCreateBitmapFromResource(HINSTANCE hInstance,
365     GDIPCONST WCHAR* lpBitmapName, GpBitmap** bitmap)
366 {
367     HBITMAP hbm;
368     GpStatus stat = InvalidParameter;
369
370     TRACE("%p (%s) %p\n", hInstance, debugstr_w(lpBitmapName), bitmap);
371
372     if(!lpBitmapName || !bitmap)
373         return InvalidParameter;
374
375     /* load DIB */
376     hbm = (HBITMAP)LoadImageW(hInstance,lpBitmapName,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION);
377
378     if(hbm){
379         stat = GdipCreateBitmapFromHBITMAP(hbm, NULL, bitmap);
380         DeleteObject(hbm);
381     }
382
383     return stat;
384 }
385
386 GpStatus WINGDIPAPI GdipCreateHBITMAPFromBitmap(GpBitmap* bitmap,
387     HBITMAP* hbmReturn, ARGB background)
388 {
389     FIXME("stub\n");
390
391     hbmReturn = NULL;
392
393     return NotImplemented;
394 }
395
396 GpStatus WINGDIPAPI GdipConvertToEmfPlus(const GpGraphics* ref,
397     GpMetafile* metafile, BOOL* succ, EmfType emfType,
398     const WCHAR* description, GpMetafile** out_metafile)
399 {
400     static int calls;
401
402     if(!ref || !metafile || !out_metafile)
403         return InvalidParameter;
404
405     *succ = FALSE;
406     *out_metafile = NULL;
407
408     if(!(calls++))
409         FIXME("not implemented\n");
410
411     return NotImplemented;
412 }
413
414 /* FIXME: this should create a bitmap in the given size with the attributes
415  * (resolution etc.) of the graphics object */
416 GpStatus WINGDIPAPI GdipCreateBitmapFromGraphics(INT width, INT height,
417     GpGraphics* target, GpBitmap** bitmap)
418 {
419     static int calls;
420     GpStatus ret;
421
422     if(!target || !bitmap)
423         return InvalidParameter;
424
425     if(!(calls++))
426         FIXME("hacked stub\n");
427
428     ret = GdipCreateBitmapFromScan0(width, height, 0, PixelFormat24bppRGB,
429                                     NULL, bitmap);
430
431     return ret;
432 }
433
434 GpStatus WINGDIPAPI GdipCreateBitmapFromScan0(INT width, INT height, INT stride,
435     PixelFormat format, BYTE* scan0, GpBitmap** bitmap)
436 {
437     BITMAPFILEHEADER *bmfh;
438     BITMAPINFOHEADER *bmih;
439     BYTE *buff;
440     INT datalen, size;
441     IStream *stream;
442
443     TRACE("%d %d %d %d %p %p\n", width, height, stride, format, scan0, bitmap);
444
445     if(!bitmap || width <= 0 || height <= 0 || (scan0 && (stride % 4))){
446         *bitmap = NULL;
447         return InvalidParameter;
448     }
449
450     if(scan0 && !stride)
451         return InvalidParameter;
452
453     /* FIXME: windows allows negative stride (reads backwards from scan0) */
454     if(stride < 0){
455         FIXME("negative stride\n");
456         return InvalidParameter;
457     }
458
459     *bitmap = GdipAlloc(sizeof(GpBitmap));
460     if(!*bitmap)    return OutOfMemory;
461
462     if(stride == 0){
463         stride = width * (PIXELFORMATBPP(format) / 8);
464         stride = (stride + 3) & ~3;
465     }
466
467     datalen = abs(stride * height);
468     size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + datalen;
469     buff = GdipAlloc(size);
470     if(!buff){
471         GdipFree(*bitmap);
472         return OutOfMemory;
473     }
474
475     bmfh = (BITMAPFILEHEADER*) buff;
476     bmih = (BITMAPINFOHEADER*) (bmfh + 1);
477
478     bmfh->bfType    = (((WORD)'M') << 8) + (WORD)'B';
479     bmfh->bfSize    = size;
480     bmfh->bfOffBits = size - datalen;
481
482     bmih->biSize            = sizeof(BITMAPINFOHEADER);
483     bmih->biWidth           = width;
484     bmih->biHeight          = -height;
485     /* FIXME: use the rest of the data from format */
486     bmih->biBitCount        = PIXELFORMATBPP(format);
487     bmih->biCompression     = BI_RGB;
488     bmih->biSizeImage       = datalen;
489
490     if(scan0)
491         memcpy(bmih + 1, scan0, datalen);
492     else
493         memset(bmih + 1, 0, datalen);
494
495     if(CreateStreamOnHGlobal(buff, TRUE, &stream) != S_OK){
496         ERR("could not make stream\n");
497         GdipFree(*bitmap);
498         GdipFree(buff);
499         return GenericError;
500     }
501
502     if(OleLoadPicture(stream, 0, FALSE, &IID_IPicture,
503         (LPVOID*) &((*bitmap)->image.picture)) != S_OK){
504         TRACE("Could not load picture\n");
505         IStream_Release(stream);
506         GdipFree(*bitmap);
507         GdipFree(buff);
508         return GenericError;
509     }
510
511     (*bitmap)->image.type = ImageTypeBitmap;
512     (*bitmap)->image.flags = ImageFlagsNone;
513     (*bitmap)->width = width;
514     (*bitmap)->height = height;
515     (*bitmap)->format = format;
516
517     return Ok;
518 }
519
520 GpStatus WINGDIPAPI GdipCreateBitmapFromStream(IStream* stream,
521     GpBitmap **bitmap)
522 {
523     GpStatus stat;
524
525     TRACE("%p %p\n", stream, bitmap);
526
527     stat = GdipLoadImageFromStream(stream, (GpImage**) bitmap);
528
529     if(stat != Ok)
530         return stat;
531
532     if((*bitmap)->image.type != ImageTypeBitmap){
533         IPicture_Release((*bitmap)->image.picture);
534         GdipFree(bitmap);
535         return GenericError; /* FIXME: what error to return? */
536     }
537
538     return Ok;
539 }
540
541 /* FIXME: no icm */
542 GpStatus WINGDIPAPI GdipCreateBitmapFromStreamICM(IStream* stream,
543     GpBitmap **bitmap)
544 {
545     TRACE("%p %p\n", stream, bitmap);
546
547     return GdipCreateBitmapFromStream(stream, bitmap);
548 }
549
550 GpStatus WINGDIPAPI GdipDisposeImage(GpImage *image)
551 {
552     HDC hdc;
553
554     TRACE("%p\n", image);
555
556     if(!image)
557         return InvalidParameter;
558
559     IPicture_get_CurDC(image->picture, &hdc);
560     DeleteDC(hdc);
561     IPicture_Release(image->picture);
562     if (image->type == ImageTypeBitmap)
563         GdipFree(((GpBitmap*)image)->bitmapbits);
564     GdipFree(image);
565
566     return Ok;
567 }
568
569 GpStatus WINGDIPAPI GdipFindFirstImageItem(GpImage *image, ImageItemData* item)
570 {
571     if(!image || !item)
572         return InvalidParameter;
573
574     return NotImplemented;
575 }
576
577 GpStatus WINGDIPAPI GdipGetImageBounds(GpImage *image, GpRectF *srcRect,
578     GpUnit *srcUnit)
579 {
580     TRACE("%p %p %p\n", image, srcRect, srcUnit);
581
582     if(!image || !srcRect || !srcUnit)
583         return InvalidParameter;
584     if(image->type == ImageTypeMetafile){
585         *srcRect = ((GpMetafile*)image)->bounds;
586         *srcUnit = ((GpMetafile*)image)->unit;
587     }
588     else if(image->type == ImageTypeBitmap){
589         srcRect->X = srcRect->Y = 0.0;
590         srcRect->Width = (REAL) ((GpBitmap*)image)->width;
591         srcRect->Height = (REAL) ((GpBitmap*)image)->height;
592         *srcUnit = UnitPixel;
593     }
594     else{
595         srcRect->X = srcRect->Y = 0.0;
596         srcRect->Width = ipicture_pixel_width(image->picture);
597         srcRect->Height = ipicture_pixel_height(image->picture);
598         *srcUnit = UnitPixel;
599     }
600
601     TRACE("returning (%f, %f) (%f, %f) unit type %d\n", srcRect->X, srcRect->Y,
602           srcRect->Width, srcRect->Height, *srcUnit);
603
604     return Ok;
605 }
606
607 GpStatus WINGDIPAPI GdipGetImageDimension(GpImage *image, REAL *width,
608     REAL *height)
609 {
610     TRACE("%p %p %p\n", image, width, height);
611
612     if(!image || !height || !width)
613         return InvalidParameter;
614
615     if(image->type == ImageTypeMetafile){
616         HDC hdc = GetDC(0);
617
618         *height = convert_unit(hdc, ((GpMetafile*)image)->unit) *
619                         ((GpMetafile*)image)->bounds.Height;
620
621         *width = convert_unit(hdc, ((GpMetafile*)image)->unit) *
622                         ((GpMetafile*)image)->bounds.Width;
623
624         ReleaseDC(0, hdc);
625     }
626
627     else if(image->type == ImageTypeBitmap){
628         *height = ((GpBitmap*)image)->height;
629         *width = ((GpBitmap*)image)->width;
630     }
631     else{
632         *height = ipicture_pixel_height(image->picture);
633         *width = ipicture_pixel_width(image->picture);
634     }
635
636     TRACE("returning (%f, %f)\n", *height, *width);
637     return Ok;
638 }
639
640 GpStatus WINGDIPAPI GdipGetImageGraphicsContext(GpImage *image,
641     GpGraphics **graphics)
642 {
643     HDC hdc;
644
645     TRACE("%p %p\n", image, graphics);
646
647     if(!image || !graphics)
648         return InvalidParameter;
649
650     if(image->type != ImageTypeBitmap){
651         FIXME("not implemented for image type %d\n", image->type);
652         return NotImplemented;
653     }
654
655     IPicture_get_CurDC(image->picture, &hdc);
656
657     if(!hdc){
658         hdc = CreateCompatibleDC(0);
659         IPicture_SelectPicture(image->picture, hdc, NULL, NULL);
660     }
661
662     return GdipCreateFromHDC(hdc, graphics);
663 }
664
665 GpStatus WINGDIPAPI GdipGetImageHeight(GpImage *image, UINT *height)
666 {
667     TRACE("%p %p\n", image, height);
668
669     if(!image || !height)
670         return InvalidParameter;
671
672     if(image->type == ImageTypeMetafile){
673         HDC hdc = GetDC(0);
674
675         *height = roundr(convert_unit(hdc, ((GpMetafile*)image)->unit) *
676                         ((GpMetafile*)image)->bounds.Height);
677
678         ReleaseDC(0, hdc);
679     }
680     else if(image->type == ImageTypeBitmap)
681         *height = ((GpBitmap*)image)->height;
682     else
683         *height = ipicture_pixel_height(image->picture);
684
685     TRACE("returning %d\n", *height);
686
687     return Ok;
688 }
689
690 GpStatus WINGDIPAPI GdipGetImageHorizontalResolution(GpImage *image, REAL *res)
691 {
692     static int calls;
693
694     if(!image || !res)
695         return InvalidParameter;
696
697     if(!(calls++))
698         FIXME("not implemented\n");
699
700     return NotImplemented;
701 }
702
703 /* FIXME: test this function for non-bitmap types */
704 GpStatus WINGDIPAPI GdipGetImagePixelFormat(GpImage *image, PixelFormat *format)
705 {
706     TRACE("%p %p\n", image, format);
707
708     if(!image || !format)
709         return InvalidParameter;
710
711     if(image->type != ImageTypeBitmap)
712         *format = PixelFormat24bppRGB;
713     else
714         *format = ((GpBitmap*) image)->format;
715
716     return Ok;
717 }
718
719 GpStatus WINGDIPAPI GdipGetImageRawFormat(GpImage *image, GUID *format)
720 {
721     static int calls;
722
723     if(!image || !format)
724         return InvalidParameter;
725
726     if(!(calls++))
727         FIXME("not implemented\n");
728
729     return NotImplemented;
730 }
731
732 GpStatus WINGDIPAPI GdipGetImageType(GpImage *image, ImageType *type)
733 {
734     TRACE("%p %p\n", image, type);
735
736     if(!image || !type)
737         return InvalidParameter;
738
739     *type = image->type;
740
741     return Ok;
742 }
743
744 GpStatus WINGDIPAPI GdipGetImageVerticalResolution(GpImage *image, REAL *res)
745 {
746     static int calls;
747
748     if(!image || !res)
749         return InvalidParameter;
750
751     if(!(calls++))
752         FIXME("not implemented\n");
753
754     return NotImplemented;
755 }
756
757 GpStatus WINGDIPAPI GdipGetImageWidth(GpImage *image, UINT *width)
758 {
759     TRACE("%p %p\n", image, width);
760
761     if(!image || !width)
762         return InvalidParameter;
763
764     if(image->type == ImageTypeMetafile){
765         HDC hdc = GetDC(0);
766
767         *width = roundr(convert_unit(hdc, ((GpMetafile*)image)->unit) *
768                         ((GpMetafile*)image)->bounds.Width);
769
770         ReleaseDC(0, hdc);
771     }
772     else if(image->type == ImageTypeBitmap)
773         *width = ((GpBitmap*)image)->width;
774     else
775         *width = ipicture_pixel_width(image->picture);
776
777     TRACE("returning %d\n", *width);
778
779     return Ok;
780 }
781
782 GpStatus WINGDIPAPI GdipGetMetafileHeaderFromMetafile(GpMetafile * metafile,
783     MetafileHeader * header)
784 {
785     static int calls;
786
787     if(!metafile || !header)
788         return InvalidParameter;
789
790     if(!(calls++))
791         FIXME("not implemented\n");
792
793     return Ok;
794 }
795
796 GpStatus WINGDIPAPI GdipGetAllPropertyItems(GpImage *image, UINT size,
797     UINT num, PropertyItem* items)
798 {
799     static int calls;
800
801     if(!(calls++))
802         FIXME("not implemented\n");
803
804     return InvalidParameter;
805 }
806
807 GpStatus WINGDIPAPI GdipGetPropertyCount(GpImage *image, UINT* num)
808 {
809     static int calls;
810
811     if(!(calls++))
812         FIXME("not implemented\n");
813
814     return InvalidParameter;
815 }
816
817 GpStatus WINGDIPAPI GdipGetPropertyIdList(GpImage *image, UINT num, PROPID* list)
818 {
819     static int calls;
820
821     if(!(calls++))
822         FIXME("not implemented\n");
823
824     return InvalidParameter;
825 }
826
827 GpStatus WINGDIPAPI GdipGetPropertyItem(GpImage *image, PROPID id, UINT size,
828     PropertyItem* buffer)
829 {
830     static int calls;
831
832     if(!(calls++))
833         FIXME("not implemented\n");
834
835     return InvalidParameter;
836 }
837
838 GpStatus WINGDIPAPI GdipGetPropertyItemSize(GpImage *image, PROPID pid,
839     UINT* size)
840 {
841     static int calls;
842
843     TRACE("%p %x %p\n", image, pid, size);
844
845     if(!size || !image)
846         return InvalidParameter;
847
848     if(!(calls++))
849         FIXME("not implemented\n");
850
851     return NotImplemented;
852 }
853
854 GpStatus WINGDIPAPI GdipGetPropertySize(GpImage *image, UINT* size, UINT* num)
855 {
856     static int calls;
857
858     if(!(calls++))
859         FIXME("not implemented\n");
860
861     return InvalidParameter;
862 }
863
864 GpStatus WINGDIPAPI GdipImageGetFrameCount(GpImage *image,
865     GDIPCONST GUID* dimensionID, UINT* count)
866 {
867     static int calls;
868
869     if(!image || !dimensionID || !count)
870         return InvalidParameter;
871
872     if(!(calls++))
873         FIXME("not implemented\n");
874
875     return NotImplemented;
876 }
877
878 GpStatus WINGDIPAPI GdipImageGetFrameDimensionsCount(GpImage *image,
879     UINT* count)
880 {
881     if(!image || !count)
882         return InvalidParameter;
883
884     *count = 1;
885
886     FIXME("stub\n");
887
888     return Ok;
889 }
890
891 GpStatus WINGDIPAPI GdipImageGetFrameDimensionsList(GpImage* image,
892     GUID* dimensionIDs, UINT count)
893 {
894     static int calls;
895
896     if(!image || !dimensionIDs)
897         return InvalidParameter;
898
899     if(!(calls++))
900         FIXME("not implemented\n");
901
902     return Ok;
903 }
904
905 GpStatus WINGDIPAPI GdipImageSelectActiveFrame(GpImage *image,
906     GDIPCONST GUID* dimensionID, UINT frameidx)
907 {
908     static int calls;
909
910     if(!image || !dimensionID)
911         return InvalidParameter;
912
913     if(!(calls++))
914         FIXME("not implemented\n");
915
916     return Ok;
917 }
918
919 GpStatus WINGDIPAPI GdipLoadImageFromFile(GDIPCONST WCHAR* filename,
920                                           GpImage **image)
921 {
922     GpStatus stat;
923     IStream *stream;
924
925     TRACE("(%s) %p\n", debugstr_w(filename), image);
926
927     if (!filename || !image)
928         return InvalidParameter;
929
930     stat = GdipCreateStreamOnFile(filename, GENERIC_READ, &stream);
931
932     if (stat != Ok)
933         return stat;
934
935     stat = GdipLoadImageFromStream(stream, image);
936
937     IStream_Release(stream);
938
939     return stat;
940 }
941
942 /* FIXME: no icm handling */
943 GpStatus WINGDIPAPI GdipLoadImageFromFileICM(GDIPCONST WCHAR* filename,GpImage **image)
944 {
945     TRACE("(%s) %p\n", debugstr_w(filename), image);
946
947     return GdipLoadImageFromFile(filename, image);
948 }
949
950 GpStatus WINGDIPAPI GdipLoadImageFromStream(IStream* stream, GpImage **image)
951 {
952     IPicture *pic;
953     short type;
954
955     TRACE("%p %p\n", stream, image);
956
957     if(!stream || !image)
958         return InvalidParameter;
959
960     if(OleLoadPicture(stream, 0, FALSE, &IID_IPicture,
961         (LPVOID*) &pic) != S_OK){
962         TRACE("Could not load picture\n");
963         return GenericError;
964     }
965
966     IPicture_get_Type(pic, &type);
967
968     if(type == PICTYPE_BITMAP){
969         BITMAPINFO bmi;
970         BITMAPCOREHEADER* bmch;
971         OLE_HANDLE hbm;
972         HDC hdc;
973
974         *image = GdipAlloc(sizeof(GpBitmap));
975         if(!*image) return OutOfMemory;
976         (*image)->type = ImageTypeBitmap;
977
978         (*((GpBitmap**) image))->width = ipicture_pixel_width(pic);
979         (*((GpBitmap**) image))->height = ipicture_pixel_height(pic);
980
981         /* get the pixel format */
982         IPicture_get_Handle(pic, &hbm);
983         IPicture_get_CurDC(pic, &hdc);
984
985         ZeroMemory(&bmi, sizeof(bmi));
986         bmch = (BITMAPCOREHEADER*) (&bmi.bmiHeader);
987         bmch->bcSize = sizeof(BITMAPCOREHEADER);
988
989         if(!hdc){
990             HBITMAP old;
991             hdc = CreateCompatibleDC(0);
992             old = SelectObject(hdc, (HBITMAP)hbm);
993             GetDIBits(hdc, (HBITMAP)hbm, 0, 0, NULL, &bmi, DIB_RGB_COLORS);
994             SelectObject(hdc, old);
995             DeleteDC(hdc);
996         }
997         else
998             GetDIBits(hdc, (HBITMAP)hbm, 0, 0, NULL, &bmi, DIB_RGB_COLORS);
999
1000         (*((GpBitmap**) image))->format = (bmch->bcBitCount << 8) | PixelFormatGDI;
1001     }
1002     else if(type == PICTYPE_METAFILE || type == PICTYPE_ENHMETAFILE){
1003         /* FIXME: missing initialization code */
1004         *image = GdipAlloc(sizeof(GpMetafile));
1005         if(!*image) return OutOfMemory;
1006         (*image)->type = ImageTypeMetafile;
1007     }
1008     else{
1009         *image = GdipAlloc(sizeof(GpImage));
1010         if(!*image) return OutOfMemory;
1011         (*image)->type = ImageTypeUnknown;
1012     }
1013
1014     (*image)->picture = pic;
1015     (*image)->flags   = ImageFlagsNone;
1016
1017     return Ok;
1018 }
1019
1020 /* FIXME: no ICM */
1021 GpStatus WINGDIPAPI GdipLoadImageFromStreamICM(IStream* stream, GpImage **image)
1022 {
1023     TRACE("%p %p\n", stream, image);
1024
1025     return GdipLoadImageFromStream(stream, image);
1026 }
1027
1028 GpStatus WINGDIPAPI GdipRemovePropertyItem(GpImage *image, PROPID propId)
1029 {
1030     static int calls;
1031
1032     if(!image)
1033         return InvalidParameter;
1034
1035     if(!(calls++))
1036         FIXME("not implemented\n");
1037
1038     return NotImplemented;
1039 }
1040
1041 GpStatus WINGDIPAPI GdipSetPropertyItem(GpImage *image, GDIPCONST PropertyItem* item)
1042 {
1043     static int calls;
1044
1045     if(!(calls++))
1046         FIXME("not implemented\n");
1047
1048     return NotImplemented;
1049 }
1050
1051 GpStatus WINGDIPAPI GdipSaveImageToFile(GpImage *image, GDIPCONST WCHAR* filename,
1052                                         GDIPCONST CLSID *clsidEncoder,
1053                                         GDIPCONST EncoderParameters *encoderParams)
1054 {
1055     GpStatus stat;
1056     IStream *stream;
1057
1058     TRACE("%p (%s) %p %p\n", image, debugstr_w(filename), clsidEncoder, encoderParams);
1059
1060     if (!image || !filename|| !clsidEncoder)
1061         return InvalidParameter;
1062
1063     if (!(image->picture))
1064         return InvalidParameter;
1065
1066     stat = GdipCreateStreamOnFile(filename, GENERIC_WRITE, &stream);
1067     if (stat != Ok)
1068         return GenericError;
1069
1070     stat = GdipSaveImageToStream(image, stream, clsidEncoder, encoderParams);
1071
1072     IStream_Release(stream);
1073     return stat;
1074 }
1075
1076 /*************************************************************************
1077  * Encoding functions -
1078  *   These functions encode an image in different image file formats.
1079  */
1080 #define BITMAP_FORMAT_BMP   0x4d42 /* "BM" */
1081 #define BITMAP_FORMAT_JPEG  0xd8ff
1082 #define BITMAP_FORMAT_GIF   0x4947
1083 #define BITMAP_FORMAT_PNG   0x5089
1084 #define BITMAP_FORMAT_APM   0xcdd7
1085
1086 static GpStatus encode_image_BMP(LPVOID bitmap_bits, LPBITMAPINFO bitmap_info,
1087                                  void **output, unsigned int *output_size)
1088 {
1089     int num_palette_entries;
1090     BITMAPFILEHEADER *bmp_file_hdr;
1091     BITMAPINFO *bmp_info_hdr;
1092
1093     if (bitmap_info->bmiHeader.biClrUsed) {
1094         num_palette_entries = bitmap_info->bmiHeader.biClrUsed;
1095         if (num_palette_entries > 256) num_palette_entries = 256;
1096     } else {
1097         if (bitmap_info->bmiHeader.biBitCount <= 8)
1098             num_palette_entries = 1 << bitmap_info->bmiHeader.biBitCount;
1099         else
1100             num_palette_entries = 0;
1101     }
1102
1103     *output_size =
1104         sizeof(BITMAPFILEHEADER) +
1105         sizeof(BITMAPINFOHEADER) +
1106         num_palette_entries * sizeof(RGBQUAD) +
1107         bitmap_info->bmiHeader.biSizeImage;
1108
1109     *output = GdipAlloc(*output_size);
1110
1111     bmp_file_hdr = (BITMAPFILEHEADER*) *output;
1112     bmp_file_hdr->bfType = BITMAP_FORMAT_BMP;
1113     bmp_file_hdr->bfSize = *output_size;
1114     bmp_file_hdr->bfOffBits =
1115         sizeof(BITMAPFILEHEADER) +
1116         sizeof(BITMAPINFOHEADER) +
1117         num_palette_entries * sizeof (RGBQUAD);
1118
1119     bmp_info_hdr = (BITMAPINFO*) ((unsigned char*)(*output) + sizeof(BITMAPFILEHEADER));
1120     memcpy(bmp_info_hdr, bitmap_info, sizeof(BITMAPINFOHEADER) + num_palette_entries * sizeof(RGBQUAD));
1121     memcpy((unsigned char *)(*output) +
1122            sizeof(BITMAPFILEHEADER) +
1123            sizeof(BITMAPINFOHEADER) +
1124            num_palette_entries * sizeof(RGBQUAD),
1125            bitmap_bits, bitmap_info->bmiHeader.biSizeImage);
1126
1127     return Ok;
1128 }
1129
1130 typedef GpStatus encode_image_func(LPVOID bitmap_bits, LPBITMAPINFO bitmap_info,
1131                                    void **output, unsigned int *output_size);
1132
1133 typedef enum {
1134     BMP,
1135     NUM_ENCODERS_SUPPORTED
1136 } ImageFormat;
1137
1138 static const ImageCodecInfo codecs[NUM_ENCODERS_SUPPORTED];
1139 static encode_image_func *const encode_image_funcs[NUM_ENCODERS_SUPPORTED] = {
1140     encode_image_BMP,
1141 };
1142
1143 /*****************************************************************************
1144  * GdipSaveImageToStream [GDIPLUS.@]
1145  */
1146 GpStatus WINGDIPAPI GdipSaveImageToStream(GpImage *image, IStream* stream,
1147     GDIPCONST CLSID* clsid, GDIPCONST EncoderParameters* params)
1148 {
1149     GpStatus stat;
1150     HRESULT hr;
1151     short type;
1152     HBITMAP hbmp;
1153     HBITMAP old_hbmp;
1154     HDC hdc;
1155     int bm_is_selected;
1156     BITMAPINFO bmp_info;
1157     LPVOID bmp_bits;
1158     encode_image_func* encode_image;
1159     LPVOID output;
1160     unsigned int output_size;
1161     unsigned int dummy;
1162     int i;
1163
1164     old_hbmp = 0;
1165     output = NULL;
1166     output_size = 0;
1167
1168     TRACE("%p %p %p %p\n", image, stream, clsid, params);
1169
1170     if(!image || !stream)
1171         return InvalidParameter;
1172
1173     if (!image->picture)
1174         return GenericError;
1175
1176     hr = IPicture_get_Type(image->picture, &type);
1177     if (FAILED(hr) || type != PICTYPE_BITMAP)
1178         return GenericError;
1179
1180     /* select correct encoder */
1181     encode_image = NULL;
1182     for (i = 0; i < NUM_ENCODERS_SUPPORTED; i++) {
1183         if (IsEqualCLSID(clsid, &codecs[i].Clsid))
1184             encode_image = encode_image_funcs[i];
1185     }
1186     if (encode_image == NULL)
1187         return UnknownImageFormat;
1188
1189     /* extract underlying hbitmap representation from the IPicture */
1190     hr = IPicture_get_Handle(image->picture, (OLE_HANDLE*)&hbmp);
1191     if (FAILED(hr) || !hbmp)
1192         return GenericError;
1193     hr = IPicture_get_CurDC(image->picture, &hdc);
1194     if (FAILED(hr))
1195         return GenericError;
1196     bm_is_selected = (hdc != 0);
1197     if (!bm_is_selected) {
1198         hdc = CreateCompatibleDC(0);
1199         old_hbmp = SelectObject(hdc, hbmp);
1200     }
1201
1202     /* get bits from HBITMAP */
1203     bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader);
1204     bmp_info.bmiHeader.biBitCount = 0;
1205     GetDIBits(hdc, hbmp, 0, 0, NULL, &bmp_info, DIB_RGB_COLORS);
1206
1207     bmp_bits = GdipAlloc(bmp_info.bmiHeader.biSizeImage);
1208
1209     if (bmp_bits)
1210         GetDIBits(hdc, hbmp, 0, abs(bmp_info.bmiHeader.biHeight), bmp_bits, &bmp_info, DIB_RGB_COLORS);
1211
1212     if (!bm_is_selected) {
1213         SelectObject(hdc, old_hbmp);
1214         DeleteDC(hdc);
1215     }
1216
1217     if (!bmp_bits)
1218         return OutOfMemory;
1219
1220     stat = encode_image(bmp_bits, &bmp_info, &output, &output_size);
1221     if (stat == Ok)
1222         IStream_Write(stream, output, output_size, &dummy);
1223
1224     GdipFree(output);
1225     GdipFree(bmp_bits);
1226
1227     return stat;
1228 }
1229
1230 /*****************************************************************************
1231  * GdipSetImagePalette [GDIPLUS.@]
1232  */
1233 GpStatus WINGDIPAPI GdipSetImagePalette(GpImage *image,
1234     GDIPCONST ColorPalette *palette)
1235 {
1236     static int calls;
1237
1238     if(!image || !palette)
1239         return InvalidParameter;
1240
1241     if(!(calls++))
1242         FIXME("not implemented\n");
1243
1244     return NotImplemented;
1245 }
1246
1247 /*************************************************************************
1248  * Encoders -
1249  *   Structures that represent which formats we support for encoding.
1250  */
1251
1252 /* ImageCodecInfo creation routines taken from libgdiplus */
1253 static const WCHAR bmp_codecname[] = {'B', 'u', 'i','l', 't', '-','i', 'n', ' ', 'B', 'M', 'P', 0}; /* Built-in BMP */
1254 static const WCHAR bmp_extension[] = {'*','.','B', 'M', 'P',';', '*','.', 'D','I', 'B',';', '*','.', 'R', 'L', 'E',0}; /* *.BMP;*.DIB;*.RLE */
1255 static const WCHAR bmp_mimetype[] = {'i', 'm', 'a','g', 'e', '/', 'b', 'm', 'p', 0}; /* image/bmp */
1256 static const WCHAR bmp_format[] = {'B', 'M', 'P', 0}; /* BMP */
1257 static const BYTE bmp_sig_pattern[] = { 0x42, 0x4D };
1258 static const BYTE bmp_sig_mask[] = { 0xFF, 0xFF };
1259
1260 static const ImageCodecInfo codecs[NUM_ENCODERS_SUPPORTED] =
1261     {
1262         { /* BMP */
1263             /* Clsid */              { 0x557cf400, 0x1a04, 0x11d3, { 0x9a, 0x73, 0x0, 0x0, 0xf8, 0x1e, 0xf3, 0x2e } },
1264             /* FormatID */           { 0xb96b3cabU, 0x0728U, 0x11d3U, {0x9d, 0x7b, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e} },
1265             /* CodecName */          bmp_codecname,
1266             /* DllName */            NULL,
1267             /* FormatDescription */  bmp_format,
1268             /* FilenameExtension */  bmp_extension,
1269             /* MimeType */           bmp_mimetype,
1270             /* Flags */              ImageCodecFlagsEncoder | ImageCodecFlagsDecoder | ImageCodecFlagsSupportBitmap | ImageCodecFlagsBuiltin,
1271             /* Version */            1,
1272             /* SigCount */           1,
1273             /* SigSize */            2,
1274             /* SigPattern */         bmp_sig_pattern,
1275             /* SigMask */            bmp_sig_mask,
1276         },
1277     };
1278
1279 /*****************************************************************************
1280  * GdipGetImageDecodersSize [GDIPLUS.@]
1281  */
1282 GpStatus WINGDIPAPI GdipGetImageDecodersSize(UINT *numDecoders, UINT *size)
1283 {
1284     FIXME("%p %p stub!\n", numDecoders, size);
1285
1286     if (!numDecoders || !size)
1287         return InvalidParameter;
1288
1289     *numDecoders = 0;
1290     *size = 0;
1291
1292     return Ok;
1293 }
1294
1295 /*****************************************************************************
1296  * GdipGetImageDecoders [GDIPLUS.@]
1297  */
1298 GpStatus WINGDIPAPI GdipGetImageDecoders(UINT numDecoders, UINT size, ImageCodecInfo *decoders)
1299 {
1300     FIXME("%u %u %p stub!\n", numDecoders, size, decoders);
1301
1302     if (!decoders)
1303         return GenericError;
1304
1305     return NotImplemented;
1306 }
1307
1308 /*****************************************************************************
1309  * GdipGetImageEncodersSize [GDIPLUS.@]
1310  */
1311 GpStatus WINGDIPAPI GdipGetImageEncodersSize(UINT *numEncoders, UINT *size)
1312 {
1313     TRACE("%p %p\n", numEncoders, size);
1314
1315     if (!numEncoders || !size)
1316         return InvalidParameter;
1317
1318     *numEncoders = NUM_ENCODERS_SUPPORTED;
1319     *size = sizeof (codecs);
1320
1321     return Ok;
1322 }
1323
1324 /*****************************************************************************
1325  * GdipGetImageEncoders [GDIPLUS.@]
1326  */
1327 GpStatus WINGDIPAPI GdipGetImageEncoders(UINT numEncoders, UINT size, ImageCodecInfo *encoders)
1328 {
1329     TRACE("%u %u %p\n", numEncoders, size, encoders);
1330
1331     if (!encoders ||
1332         (numEncoders != NUM_ENCODERS_SUPPORTED) ||
1333         (size != sizeof (codecs)))
1334         return GenericError;
1335
1336     memcpy(encoders, codecs, sizeof (codecs));
1337
1338     return Ok;
1339 }
1340
1341 /*****************************************************************************
1342  * GdipCreateBitmapFromHBITMAP [GDIPLUS.@]
1343  */
1344 GpStatus WINGDIPAPI GdipCreateBitmapFromHBITMAP(HBITMAP hbm, HPALETTE hpal, GpBitmap** bitmap)
1345 {
1346     BITMAP bm;
1347     GpStatus retval;
1348     PixelFormat format;
1349
1350     TRACE("%p %p %p\n", hbm, hpal, bitmap);
1351
1352     if(!hbm || !bitmap)
1353         return InvalidParameter;
1354
1355     /* TODO: Support for device-dependent bitmaps */
1356     if(hpal){
1357         FIXME("no support for device-dependent bitmaps\n");
1358         return NotImplemented;
1359     }
1360
1361     if (GetObjectA(hbm, sizeof(bm), &bm) != sizeof(bm))
1362             return InvalidParameter;
1363
1364     /* TODO: Figure out the correct format for 16, 32, 64 bpp */
1365     switch(bm.bmBitsPixel) {
1366         case 1:
1367             format = PixelFormat1bppIndexed;
1368             break;
1369         case 4:
1370             format = PixelFormat4bppIndexed;
1371             break;
1372         case 8:
1373             format = PixelFormat8bppIndexed;
1374             break;
1375         case 24:
1376             format = PixelFormat24bppRGB;
1377             break;
1378         case 48:
1379             format = PixelFormat48bppRGB;
1380             break;
1381         default:
1382             FIXME("don't know how to handle %d bpp\n", bm.bmBitsPixel);
1383             return InvalidParameter;
1384     }
1385
1386     retval = GdipCreateBitmapFromScan0(bm.bmWidth, bm.bmHeight, bm.bmWidthBytes,
1387         format, bm.bmBits, bitmap);
1388
1389     return retval;
1390 }
1391
1392 /*****************************************************************************
1393  * GdipSetEffectParameters [GDIPLUS.@]
1394  */
1395 GpStatus WINGDIPAPI GdipSetEffectParameters(CGpEffect *effect,
1396     const VOID *params, const UINT size)
1397 {
1398     static int calls;
1399
1400     if(!(calls++))
1401         FIXME("not implemented\n");
1402
1403     return NotImplemented;
1404 }
1405
1406 /*****************************************************************************
1407  * GdipGetImageFlags [GDIPLUS.@]
1408  */
1409 GpStatus WINGDIPAPI GdipGetImageFlags(GpImage *image, UINT *flags)
1410 {
1411     TRACE("%p %p\n", image, flags);
1412
1413     if(!image || !flags)
1414         return InvalidParameter;
1415
1416     *flags = image->flags;
1417
1418     return Ok;
1419 }
1420
1421 GpStatus WINGDIPAPI GdipTestControl(GpTestControlEnum control, void *param)
1422 {
1423     TRACE("(%d, %p)\n", control, param);
1424
1425     switch(control){
1426         case TestControlForceBilinear:
1427             if(param)
1428                 FIXME("TestControlForceBilinear not handled\n");
1429             break;
1430         case TestControlNoICM:
1431             if(param)
1432                 FIXME("TestControlNoICM not handled\n");
1433             break;
1434         case TestControlGetBuildNumber:
1435             *((DWORD*)param) = 3102;
1436             break;
1437     }
1438
1439     return Ok;
1440 }