gdiplus: Better error checking in GdipBitmapLockBits.
[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 typedef void ImageItemData;
38
39 #define PIXELFORMATBPP(x) ((x) ? ((x) >> 8) & 255 : 24)
40
41 static INT ipicture_pixel_height(IPicture *pic)
42 {
43     HDC hdcref;
44     OLE_YSIZE_HIMETRIC y;
45
46     IPicture_get_Height(pic, &y);
47
48     hdcref = GetDC(0);
49
50     y = (UINT)(((REAL)y) * ((REAL)GetDeviceCaps(hdcref, LOGPIXELSY)) /
51               ((REAL)INCH_HIMETRIC));
52     ReleaseDC(0, hdcref);
53
54     return y;
55 }
56
57 static INT ipicture_pixel_width(IPicture *pic)
58 {
59     HDC hdcref;
60     OLE_XSIZE_HIMETRIC x;
61
62     IPicture_get_Width(pic, &x);
63
64     hdcref = GetDC(0);
65
66     x = (UINT)(((REAL)x) * ((REAL)GetDeviceCaps(hdcref, LOGPIXELSX)) /
67               ((REAL)INCH_HIMETRIC));
68
69     ReleaseDC(0, hdcref);
70
71     return x;
72 }
73
74 GpStatus WINGDIPAPI GdipBitmapGetPixel(GpBitmap* bitmap, INT x, INT y,
75     ARGB *color)
76 {
77     static int calls;
78     TRACE("%p %d %d %p\n", bitmap, x, y, color);
79
80     if(!bitmap || !color)
81         return InvalidParameter;
82
83     if(!(calls++))
84         FIXME("not implemented\n");
85
86     *color = 0xdeadbeef;
87
88     return NotImplemented;
89 }
90
91 /* This function returns a pointer to an array of pixels that represents the
92  * bitmap. The *entire* bitmap is locked according to the lock mode specified by
93  * flags.  It is correct behavior that a user who calls this function with write
94  * privileges can write to the whole bitmap (not just the area in rect).
95  *
96  * FIXME: only used portion of format is bits per pixel. */
97 GpStatus WINGDIPAPI GdipBitmapLockBits(GpBitmap* bitmap, GDIPCONST GpRect* rect,
98     UINT flags, PixelFormat format, BitmapData* lockeddata)
99 {
100     BOOL bm_is_selected;
101     INT stride, bitspp = PIXELFORMATBPP(format);
102     OLE_HANDLE hbm;
103     HDC hdc;
104     HBITMAP old = NULL;
105     BITMAPINFO bmi;
106     BYTE *buff = NULL;
107     UINT abs_height;
108
109     TRACE("%p %p %d %d %p\n", bitmap, rect, flags, format, lockeddata);
110
111     if(!lockeddata || !bitmap || !rect)
112         return InvalidParameter;
113
114     if(rect->X < 0 || rect->Y < 0 || (rect->X + rect->Width > bitmap->width) ||
115        (rect->Y + rect->Height > bitmap->height) || !flags)
116         return InvalidParameter;
117
118     if(flags & ImageLockModeUserInputBuf)
119         return NotImplemented;
120
121     if((bitmap->lockmode & ImageLockModeWrite) || (bitmap->lockmode &&
122         (flags & ImageLockModeWrite)))
123         return WrongState;
124
125     IPicture_get_Handle(bitmap->image.picture, &hbm);
126     IPicture_get_CurDC(bitmap->image.picture, &hdc);
127     bm_is_selected = (hdc != 0);
128
129     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
130     bmi.bmiHeader.biBitCount = 0;
131
132     if(!bm_is_selected){
133         hdc = CreateCompatibleDC(0);
134         old = SelectObject(hdc, (HBITMAP)hbm);
135     }
136
137     /* fill out bmi */
138     GetDIBits(hdc, (HBITMAP)hbm, 0, 0, NULL, &bmi, DIB_RGB_COLORS);
139
140     abs_height = abs(bmi.bmiHeader.biHeight);
141     stride = bmi.bmiHeader.biWidth * bitspp / 8;
142     stride = (stride + 3) & ~3;
143
144     buff = GdipAlloc(stride * abs_height);
145
146     bmi.bmiHeader.biBitCount = bitspp;
147
148     if(buff)
149         GetDIBits(hdc, (HBITMAP)hbm, 0, abs_height, buff, &bmi, DIB_RGB_COLORS);
150
151     if(!bm_is_selected){
152         SelectObject(hdc, old);
153         DeleteDC(hdc);
154     }
155
156     if(!buff)
157         return OutOfMemory;
158
159     lockeddata->Width = rect->Width;
160     lockeddata->Height = rect->Height;
161     lockeddata->PixelFormat = format;
162     lockeddata->Reserved = flags;
163
164     if(bmi.bmiHeader.biHeight > 0){
165         lockeddata->Stride = -stride;
166         lockeddata->Scan0 = buff + (bitspp / 8) * rect->X +
167                             stride * (abs_height - 1 - rect->Y);
168     }
169     else{
170         lockeddata->Stride = stride;
171         lockeddata->Scan0 = buff + (bitspp / 8) * rect->X + stride * rect->Y;
172     }
173
174     bitmap->lockmode = flags;
175     bitmap->numlocks++;
176
177     if(flags & ImageLockModeWrite)
178         bitmap->bitmapbits = buff;
179
180     return Ok;
181 }
182
183 GpStatus WINGDIPAPI GdipBitmapUnlockBits(GpBitmap* bitmap,
184     BitmapData* lockeddata)
185 {
186     OLE_HANDLE hbm;
187     HDC hdc;
188     HBITMAP old = NULL;
189     BOOL bm_is_selected;
190     BITMAPINFO bmi;
191
192     if(!bitmap || !lockeddata)
193         return InvalidParameter;
194
195     if(!bitmap->lockmode)
196         return WrongState;
197
198     if(lockeddata->Reserved & ImageLockModeUserInputBuf)
199         return NotImplemented;
200
201     if(lockeddata->Reserved & ImageLockModeRead){
202         if(!(--bitmap->numlocks))
203             bitmap->lockmode = 0;
204
205         GdipFree(lockeddata->Scan0);
206         return Ok;
207     }
208
209     IPicture_get_Handle(bitmap->image.picture, &hbm);
210     IPicture_get_CurDC(bitmap->image.picture, &hdc);
211     bm_is_selected = (hdc != 0);
212
213     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
214     bmi.bmiHeader.biBitCount = 0;
215
216     if(!bm_is_selected){
217         hdc = CreateCompatibleDC(0);
218         old = SelectObject(hdc, (HBITMAP)hbm);
219     }
220
221     GetDIBits(hdc, (HBITMAP)hbm, 0, 0, NULL, &bmi, DIB_RGB_COLORS);
222     bmi.bmiHeader.biBitCount = PIXELFORMATBPP(lockeddata->PixelFormat);
223     SetDIBits(hdc, (HBITMAP)hbm, 0, abs(bmi.bmiHeader.biHeight),
224               bitmap->bitmapbits, &bmi, DIB_RGB_COLORS);
225
226     if(!bm_is_selected){
227         SelectObject(hdc, old);
228         DeleteDC(hdc);
229     }
230
231     GdipFree(bitmap->bitmapbits);
232
233     return Ok;
234 }
235
236 GpStatus WINGDIPAPI GdipCreateBitmapFromFile(GDIPCONST WCHAR* filename,
237     GpBitmap **bitmap)
238 {
239     GpStatus stat;
240     IStream *stream;
241
242     if(!filename || !bitmap)
243         return InvalidParameter;
244
245     stat = GdipCreateStreamOnFile(filename, GENERIC_READ, &stream);
246
247     if(stat != Ok)
248         return stat;
249
250     stat = GdipCreateBitmapFromStream(stream, bitmap);
251
252     if(!stat)
253         IStream_Release(stream);
254
255     return stat;
256 }
257
258 /* FIXME: this should create a bitmap in the given size with the attributes
259  * (resolution etc.) of the graphics object */
260 GpStatus WINGDIPAPI GdipCreateBitmapFromGraphics(INT width, INT height,
261     GpGraphics* target, GpBitmap** bitmap)
262 {
263     static int calls;
264     GpStatus ret;
265
266     if(!target || !bitmap)
267         return InvalidParameter;
268
269     if(!(calls++))
270         FIXME("hacked stub\n");
271
272     ret = GdipCreateBitmapFromScan0(width, height, 0, PixelFormat24bppRGB,
273                                     NULL, bitmap);
274
275     return ret;
276 }
277
278 GpStatus WINGDIPAPI GdipCreateBitmapFromScan0(INT width, INT height, INT stride,
279     PixelFormat format, BYTE* scan0, GpBitmap** bitmap)
280 {
281     BITMAPFILEHEADER *bmfh;
282     BITMAPINFOHEADER *bmih;
283     BYTE *buff;
284     INT datalen, size;
285     IStream *stream;
286
287     TRACE("%d %d %d %d %p %p\n", width, height, stride, format, scan0, bitmap);
288
289     if(!bitmap || width <= 0 || height <= 0 || (scan0 && (stride % 4))){
290         *bitmap = NULL;
291         return InvalidParameter;
292     }
293
294     if(scan0 && !stride)
295         return InvalidParameter;
296
297     /* FIXME: windows allows negative stride (reads backwards from scan0) */
298     if(stride < 0){
299         FIXME("negative stride\n");
300         return InvalidParameter;
301     }
302
303     *bitmap = GdipAlloc(sizeof(GpBitmap));
304     if(!*bitmap)    return OutOfMemory;
305
306     if(stride == 0){
307         stride = width * (PIXELFORMATBPP(format) / 8);
308         stride = (stride + 3) & ~3;
309     }
310
311     datalen = abs(stride * height);
312     size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + datalen;
313     buff = GdipAlloc(size);
314     if(!buff){
315         GdipFree(*bitmap);
316         return OutOfMemory;
317     }
318
319     bmfh = (BITMAPFILEHEADER*) buff;
320     bmih = (BITMAPINFOHEADER*) (bmfh + 1);
321
322     bmfh->bfType    = (((WORD)'M') << 8) + (WORD)'B';
323     bmfh->bfSize    = size;
324     bmfh->bfOffBits = size - datalen;
325
326     bmih->biSize            = sizeof(BITMAPINFOHEADER);
327     bmih->biWidth           = width;
328     bmih->biHeight          = -height;
329     /* FIXME: use the rest of the data from format */
330     bmih->biBitCount        = PIXELFORMATBPP(format);
331     bmih->biCompression     = BI_RGB;
332     bmih->biSizeImage       = datalen;
333
334     if(scan0)
335         memcpy(bmih + 1, scan0, datalen);
336     else
337         memset(bmih + 1, 0, datalen);
338
339     if(CreateStreamOnHGlobal(buff, TRUE, &stream) != S_OK){
340         ERR("could not make stream\n");
341         GdipFree(*bitmap);
342         GdipFree(buff);
343         return GenericError;
344     }
345
346     if(OleLoadPicture(stream, 0, FALSE, &IID_IPicture,
347         (LPVOID*) &((*bitmap)->image.picture)) != S_OK){
348         TRACE("Could not load picture\n");
349         IStream_Release(stream);
350         GdipFree(*bitmap);
351         GdipFree(buff);
352         return GenericError;
353     }
354
355     (*bitmap)->image.type = ImageTypeBitmap;
356     (*bitmap)->width = width;
357     (*bitmap)->height = height;
358     (*bitmap)->format = format;
359
360     return Ok;
361 }
362
363 GpStatus WINGDIPAPI GdipCreateBitmapFromStream(IStream* stream,
364     GpBitmap **bitmap)
365 {
366     GpStatus stat;
367
368     stat = GdipLoadImageFromStream(stream, (GpImage**) bitmap);
369
370     if(stat != Ok)
371         return stat;
372
373     if((*bitmap)->image.type != ImageTypeBitmap){
374         IPicture_Release((*bitmap)->image.picture);
375         GdipFree(bitmap);
376         return GenericError; /* FIXME: what error to return? */
377     }
378
379     return Ok;
380 }
381
382 /* FIXME: no icm */
383 GpStatus WINGDIPAPI GdipCreateBitmapFromStreamICM(IStream* stream,
384     GpBitmap **bitmap)
385 {
386     return GdipCreateBitmapFromStream(stream, bitmap);
387 }
388
389 GpStatus WINGDIPAPI GdipDisposeImage(GpImage *image)
390 {
391     HDC hdc;
392
393     if(!image)
394         return InvalidParameter;
395
396     IPicture_get_CurDC(image->picture, &hdc);
397     DeleteDC(hdc);
398     IPicture_Release(image->picture);
399     GdipFree(image);
400
401     return Ok;
402 }
403
404 GpStatus WINGDIPAPI GdipFindFirstImageItem(GpImage *image, ImageItemData* item)
405 {
406     if(!image || !item)
407         return InvalidParameter;
408
409     return NotImplemented;
410 }
411
412 GpStatus WINGDIPAPI GdipGetImageBounds(GpImage *image, GpRectF *srcRect,
413     GpUnit *srcUnit)
414 {
415     if(!image || !srcRect || !srcUnit)
416         return InvalidParameter;
417     if(image->type == ImageTypeMetafile){
418         memcpy(srcRect, &((GpMetafile*)image)->bounds, sizeof(GpRectF));
419         *srcUnit = ((GpMetafile*)image)->unit;
420     }
421     else if(image->type == ImageTypeBitmap){
422         srcRect->X = srcRect->Y = 0.0;
423         srcRect->Width = (REAL) ((GpBitmap*)image)->width;
424         srcRect->Height = (REAL) ((GpBitmap*)image)->height;
425         *srcUnit = UnitPixel;
426     }
427     else{
428         srcRect->X = srcRect->Y = 0.0;
429         srcRect->Width = ipicture_pixel_width(image->picture);
430         srcRect->Height = ipicture_pixel_height(image->picture);
431         *srcUnit = UnitPixel;
432     }
433
434     TRACE("returning (%f, %f) (%f, %f) unit type %d\n", srcRect->X, srcRect->Y,
435           srcRect->Width, srcRect->Height, *srcUnit);
436
437     return Ok;
438 }
439
440 GpStatus WINGDIPAPI GdipGetImageGraphicsContext(GpImage *image,
441     GpGraphics **graphics)
442 {
443     HDC hdc;
444
445     if(!image || !graphics)
446         return InvalidParameter;
447
448     if(image->type != ImageTypeBitmap){
449         FIXME("not implemented for image type %d\n", image->type);
450         return NotImplemented;
451     }
452
453     IPicture_get_CurDC(image->picture, &hdc);
454
455     if(!hdc){
456         hdc = CreateCompatibleDC(0);
457         IPicture_SelectPicture(image->picture, hdc, NULL, NULL);
458     }
459
460     return GdipCreateFromHDC(hdc, graphics);
461 }
462
463 GpStatus WINGDIPAPI GdipGetImageHeight(GpImage *image, UINT *height)
464 {
465     if(!image || !height)
466         return InvalidParameter;
467
468     if(image->type == ImageTypeMetafile){
469         HDC hdc = GetDC(0);
470
471         *height = roundr(convert_unit(hdc, ((GpMetafile*)image)->unit) *
472                         ((GpMetafile*)image)->bounds.Height);
473
474         ReleaseDC(0, hdc);
475     }
476     else if(image->type == ImageTypeBitmap)
477         *height = ((GpBitmap*)image)->height;
478     else
479         *height = ipicture_pixel_height(image->picture);
480
481     TRACE("returning %d\n", *height);
482
483     return Ok;
484 }
485
486 GpStatus WINGDIPAPI GdipGetImageHorizontalResolution(GpImage *image, REAL *res)
487 {
488     static int calls;
489
490     if(!image || !res)
491         return InvalidParameter;
492
493     if(!(calls++))
494         FIXME("not implemented\n");
495
496     return NotImplemented;
497 }
498
499 /* FIXME: test this function for non-bitmap types */
500 GpStatus WINGDIPAPI GdipGetImagePixelFormat(GpImage *image, PixelFormat *format)
501 {
502     if(!image || !format)
503         return InvalidParameter;
504
505     if(image->type != ImageTypeBitmap)
506         *format = PixelFormat24bppRGB;
507     else
508         *format = ((GpBitmap*) image)->format;
509
510     return Ok;
511 }
512
513 GpStatus WINGDIPAPI GdipGetImageRawFormat(GpImage *image, GUID *format)
514 {
515     static int calls;
516
517     if(!image || !format)
518         return InvalidParameter;
519
520     if(!(calls++))
521         FIXME("not implemented\n");
522
523     return NotImplemented;
524 }
525
526 GpStatus WINGDIPAPI GdipGetImageType(GpImage *image, ImageType *type)
527 {
528     if(!image || !type)
529         return InvalidParameter;
530
531     *type = image->type;
532
533     return Ok;
534 }
535
536 GpStatus WINGDIPAPI GdipGetImageVerticalResolution(GpImage *image, REAL *res)
537 {
538     static int calls;
539
540     if(!image || !res)
541         return InvalidParameter;
542
543     if(!(calls++))
544         FIXME("not implemented\n");
545
546     return NotImplemented;
547 }
548
549 GpStatus WINGDIPAPI GdipGetImageWidth(GpImage *image, UINT *width)
550 {
551     if(!image || !width)
552         return InvalidParameter;
553
554     if(image->type == ImageTypeMetafile){
555         HDC hdc = GetDC(0);
556
557         *width = roundr(convert_unit(hdc, ((GpMetafile*)image)->unit) *
558                         ((GpMetafile*)image)->bounds.Width);
559
560         ReleaseDC(0, hdc);
561     }
562     else if(image->type == ImageTypeBitmap)
563         *width = ((GpBitmap*)image)->width;
564     else
565         *width = ipicture_pixel_width(image->picture);
566
567     TRACE("returning %d\n", *width);
568
569     return Ok;
570 }
571
572 GpStatus WINGDIPAPI GdipGetMetafileHeaderFromMetafile(GpMetafile * metafile,
573     MetafileHeader * header)
574 {
575     static int calls;
576
577     if(!metafile || !header)
578         return InvalidParameter;
579
580     if(!(calls++))
581         FIXME("not implemented\n");
582
583     return Ok;
584 }
585
586 GpStatus WINGDIPAPI GdipGetPropertyItemSize(GpImage *image, PROPID pid,
587     UINT* size)
588 {
589     static int calls;
590
591     TRACE("%p %x %p\n", image, pid, size);
592
593     if(!size || !image)
594         return InvalidParameter;
595
596     if(!(calls++))
597         FIXME("not implemented\n");
598
599     return NotImplemented;
600 }
601
602 GpStatus WINGDIPAPI GdipImageGetFrameCount(GpImage *image,
603     GDIPCONST GUID* dimensionID, UINT* count)
604 {
605     static int calls;
606
607     if(!image || !dimensionID || !count)
608         return InvalidParameter;
609
610     if(!(calls++))
611         FIXME("not implemented\n");
612
613     return NotImplemented;
614 }
615
616 GpStatus WINGDIPAPI GdipImageGetFrameDimensionsList(GpImage* image,
617     GUID* dimensionIDs, UINT count)
618 {
619     static int calls;
620
621     if(!image || !dimensionIDs)
622         return InvalidParameter;
623
624     if(!(calls++))
625         FIXME("not implemented\n");
626
627     return Ok;
628 }
629
630 GpStatus WINGDIPAPI GdipImageSelectActiveFrame(GpImage *image,
631     GDIPCONST GUID* dimensionID, UINT frameidx)
632 {
633     static int calls;
634
635     if(!image || !dimensionID)
636         return InvalidParameter;
637
638     if(!(calls++))
639         FIXME("not implemented\n");
640
641     return Ok;
642 }
643
644 GpStatus WINGDIPAPI GdipLoadImageFromStream(IStream* stream, GpImage **image)
645 {
646     IPicture *pic;
647     short type;
648
649     if(!stream || !image)
650         return InvalidParameter;
651
652     if(OleLoadPicture(stream, 0, FALSE, &IID_IPicture,
653         (LPVOID*) &pic) != S_OK){
654         TRACE("Could not load picture\n");
655         return GenericError;
656     }
657
658     IStream_AddRef(stream);
659
660     IPicture_get_Type(pic, &type);
661
662     if(type == PICTYPE_BITMAP){
663         BITMAPINFO bmi;
664         BITMAPCOREHEADER* bmch;
665         OLE_HANDLE hbm;
666         HDC hdc;
667
668         *image = GdipAlloc(sizeof(GpBitmap));
669         if(!*image) return OutOfMemory;
670         (*image)->type = ImageTypeBitmap;
671
672         (*((GpBitmap**) image))->width = ipicture_pixel_width(pic);
673         (*((GpBitmap**) image))->height = ipicture_pixel_height(pic);
674
675         /* get the pixel format */
676         IPicture_get_Handle(pic, &hbm);
677         IPicture_get_CurDC(pic, &hdc);
678
679         bmch = (BITMAPCOREHEADER*) (&bmi.bmiHeader);
680         bmch->bcSize = sizeof(BITMAPCOREHEADER);
681
682         if(!hdc){
683             HBITMAP old;
684             hdc = CreateCompatibleDC(0);
685             old = SelectObject(hdc, (HBITMAP)hbm);
686             GetDIBits(hdc, (HBITMAP)hbm, 0, 0, NULL, &bmi, DIB_RGB_COLORS);
687             SelectObject(hdc, old);
688             DeleteDC(hdc);
689         }
690         else
691             GetDIBits(hdc, (HBITMAP)hbm, 0, 0, NULL, &bmi, DIB_RGB_COLORS);
692
693         (*((GpBitmap**) image))->format = (bmch->bcBitCount << 8) | PixelFormatGDI;
694     }
695     else if(type == PICTYPE_METAFILE || type == PICTYPE_ENHMETAFILE){
696         /* FIXME: missing initialization code */
697         *image = GdipAlloc(sizeof(GpMetafile));
698         if(!*image) return OutOfMemory;
699         (*image)->type = ImageTypeMetafile;
700     }
701     else{
702         *image = GdipAlloc(sizeof(GpImage));
703         if(!*image) return OutOfMemory;
704         (*image)->type = ImageTypeUnknown;
705     }
706
707     (*image)->picture = pic;
708
709     return Ok;
710 }
711
712 /* FIXME: no ICM */
713 GpStatus WINGDIPAPI GdipLoadImageFromStreamICM(IStream* stream, GpImage **image)
714 {
715     return GdipLoadImageFromStream(stream, image);
716 }
717
718 GpStatus WINGDIPAPI GdipRemovePropertyItem(GpImage *image, PROPID propId)
719 {
720     static int calls;
721
722     if(!image)
723         return InvalidParameter;
724
725     if(!(calls++))
726         FIXME("not implemented\n");
727
728     return NotImplemented;
729 }
730
731 GpStatus WINGDIPAPI GdipSaveImageToStream(GpImage *image, IStream* stream,
732     GDIPCONST CLSID* clsid, GDIPCONST EncoderParameters* params)
733 {
734     if(!image || !stream)
735         return InvalidParameter;
736
737     /* FIXME: CLSID, EncoderParameters not used */
738
739     IPicture_SaveAsFile(image->picture, stream, FALSE, NULL);
740
741     return Ok;
742 }
743
744 GpStatus WINGDIPAPI GdipSetImagePalette(GpImage *image,
745     GDIPCONST ColorPalette *palette)
746 {
747     static int calls;
748
749     if(!image || !palette)
750         return InvalidParameter;
751
752     if(!(calls++))
753         FIXME("not implemented\n");
754
755     return NotImplemented;
756 }