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