 b559c7dacc
			
		
	
	b559c7dacc
	
	
	
		
			
			2008-09-18 Dominic Lachowicz <domlachowicz@gmail.com> * io-gdip-utils.c: Fix 2 cases where we leaked a GpImage (#552545) svn path=/trunk/; revision=21426
		
			
				
	
	
		
			1014 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1014 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* GdkPixbuf library - Win32 GDI+ Pixbuf Loader
 | |
|  *
 | |
|  * Copyright (C) 2008 Dominic Lachowicz
 | |
|  * Copyright (C) 2008 Alberto Ruiz
 | |
|  *
 | |
|  * Authors: Dominic Lachowicz <domlachowicz@gmail.com>
 | |
|  *          Alberto Ruiz <aruiz@gnome.org>
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Lesser General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Lesser General Public License for more  * You should have received a copy of the GNU Lesser General Public
 | |
|  * License along with this library; if not, write to the
 | |
|  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 | |
|  * Boston, MA 02111-1307, USA.
 | |
|  */
 | |
| 
 | |
| #define INITGUID
 | |
| #include <ole2.h>
 | |
| 
 | |
| #include "io-gdip-utils.h"
 | |
| #include "io-gdip-native.h"
 | |
| #include "io-gdip-propertytags.h"
 | |
| #include "io-gdip-animation.h"
 | |
| 
 | |
| #define LOAD_BUFFER_SIZE 65536
 | |
| 
 | |
| static GdiplusStartupFunc GdiplusStartup;
 | |
| static GdipCreateBitmapFromStreamFunc GdipCreateBitmapFromStream;
 | |
| static GdipBitmapGetPixelFunc GdipBitmapGetPixel;
 | |
| static GdipGetImageHeightFunc GdipGetImageHeight;
 | |
| static GdipDisposeImageFunc GdipDisposeImage;
 | |
| static GdipGetImageFlagsFunc GdipGetImageFlags;
 | |
| static GdipGetImageWidthFunc GdipGetImageWidth;
 | |
| static GdipImageGetFrameCountFunc GdipImageGetFrameCount;
 | |
| static GdipImageSelectActiveFrameFunc GdipImageSelectActiveFrame;
 | |
| static GdipGetPropertyItemSizeFunc GdipGetPropertyItemSize;
 | |
| static GdipGetPropertyItemFunc GdipGetPropertyItem;
 | |
| static GdipGetPropertyCountFunc GdipGetPropertyCount;
 | |
| static GdipGetPropertyIdListFunc GdipGetPropertyIdList;
 | |
| static GdipCreateBitmapFromScan0Func GdipCreateBitmapFromScan0;
 | |
| static GdipSaveImageToStreamFunc GdipSaveImageToStream;
 | |
| static GdipBitmapSetPixelFunc GdipBitmapSetPixel;
 | |
| static GdipDrawImageIFunc GdipDrawImageI;
 | |
| static GdipGetImageGraphicsContextFunc GdipGetImageGraphicsContext;
 | |
| static GdipFlushFunc GdipFlush;
 | |
| static GdipGraphicsClearFunc GdipGraphicsClear;
 | |
| static GdipBitmapSetResolutionFunc GdipBitmapSetResolution;
 | |
| static GdipGetImageHorizontalResolutionFunc GdipGetImageHorizontalResolution;
 | |
| static GdipGetImageVerticalResolutionFunc GdipGetImageVerticalResolution;
 | |
| static GdipLoadImageFromStreamFunc GdipLoadImageFromStream;
 | |
| static GdipDeleteGraphicsFunc GdipDeleteGraphics;
 | |
| static GdipGetImageEncodersFunc GdipGetImageEncoders;
 | |
| static GdipGetImageEncodersSizeFunc GdipGetImageEncodersSize;
 | |
| static GdipBitmapLockBitsFunc GdipBitmapLockBits;
 | |
| static GdipBitmapUnlockBitsFunc GdipBitmapUnlockBits;
 | |
| static GdipGetImagePixelFormatFunc GdipGetImagePixelFormat;
 | |
| static GdipCloneBitmapAreaIFunc GdipCloneBitmapAreaI;
 | |
| 
 | |
| DEFINE_GUID(FrameDimensionTime, 0x6aedbd6d,0x3fb5,0x418a,0x83,0xa6,0x7f,0x45,0x22,0x9d,0xc8,0x72);
 | |
| DEFINE_GUID(FrameDimensionPage, 0x7462dc86,0x6180,0x4c7e,0x8e,0x3f,0xee,0x73,0x33,0xa7,0xa4,0x83);
 | |
| 
 | |
| static void
 | |
| gdip_set_error_from_hresult (GError **error, gint code, HRESULT hr, const char *format)
 | |
| {
 | |
|   gchar *msg;
 | |
|   
 | |
|   msg = g_win32_error_message (hr);
 | |
|   
 | |
|   if (msg) {
 | |
|     g_set_error (error, GDK_PIXBUF_ERROR, code, format, msg);
 | |
|     g_free (msg);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gdip_set_error_from_gpstatus (GError **error, gint code, GpStatus status)
 | |
| {
 | |
|   const char *msg;
 | |
| 
 | |
|   switch (status)
 | |
|     {
 | |
| #define CASE(x) case x: msg = #x; break
 | |
|     CASE (GenericError);
 | |
|     CASE (InvalidParameter);
 | |
|     CASE (OutOfMemory);
 | |
|     CASE (ObjectBusy);
 | |
|     CASE (InsufficientBuffer);
 | |
|     CASE (NotImplemented);
 | |
|     CASE (Win32Error);
 | |
|     CASE (WrongState);
 | |
|     CASE (Aborted);
 | |
|     CASE (FileNotFound);
 | |
|     CASE (ValueOverflow);
 | |
|     CASE (AccessDenied);
 | |
|     CASE (UnknownImageFormat);
 | |
|     CASE (FontFamilyNotFound);
 | |
|     CASE (FontStyleNotFound);
 | |
|     CASE (NotTrueTypeFont);
 | |
|     CASE (UnsupportedGdiplusVersion);
 | |
|     CASE (GdiplusNotInitialized);
 | |
|     CASE (PropertyNotFound);
 | |
|     CASE (PropertyNotSupported);
 | |
|     CASE (ProfileNotFound);
 | |
|     default:
 | |
|       msg = "Unknown error";
 | |
|     }
 | |
|   g_set_error_literal (error, GDK_PIXBUF_ERROR, code, msg);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdip_init (void)
 | |
| {
 | |
|   GdiplusStartupInput input;
 | |
|   ULONG_PTR gdiplusToken = 0;
 | |
|   static HINSTANCE gdipluslib = NULL;
 | |
| 
 | |
|   if (!gdipluslib)
 | |
|     gdipluslib = LoadLibrary ("gdiplus.dll");
 | |
|   else
 | |
|     return TRUE; /* gdip_init() is idempotent */
 | |
| 
 | |
|   if (!gdipluslib)
 | |
|     return FALSE;
 | |
| 
 | |
| #define LOOKUP(func) \
 | |
|   G_STMT_START { \
 | |
|     func = (func##Func) GetProcAddress (gdipluslib, #func); \
 | |
|     if (!func) {\
 | |
|       g_warning ("Couldn't find GDI+ function %s\n", #func); \
 | |
|       return FALSE; \
 | |
|     } \
 | |
|   } G_STMT_END
 | |
| 
 | |
|   LOOKUP (GdiplusStartup);
 | |
|   LOOKUP (GdipCreateBitmapFromStream);
 | |
|   LOOKUP (GdipBitmapGetPixel);
 | |
|   LOOKUP (GdipGetImageHeight);
 | |
|   LOOKUP (GdipDisposeImage);
 | |
|   LOOKUP (GdipGetImageFlags);
 | |
|   LOOKUP (GdipGetImageWidth);
 | |
|   LOOKUP (GdipImageGetFrameCount);
 | |
|   LOOKUP (GdipImageSelectActiveFrame);
 | |
|   LOOKUP (GdipGetPropertyItemSize);
 | |
|   LOOKUP (GdipGetPropertyItem);
 | |
|   LOOKUP (GdipGetPropertyCount);
 | |
|   LOOKUP (GdipGetPropertyIdList);
 | |
|   LOOKUP (GdipCreateBitmapFromScan0);
 | |
|   LOOKUP (GdipSaveImageToStream);
 | |
|   LOOKUP (GdipBitmapSetPixel);
 | |
|   LOOKUP (GdipDrawImageI);
 | |
|   LOOKUP (GdipGetImageGraphicsContext);
 | |
|   LOOKUP (GdipFlush);
 | |
|   LOOKUP (GdipGraphicsClear);
 | |
|   LOOKUP (GdipBitmapSetResolution);
 | |
|   LOOKUP (GdipGetImageHorizontalResolution);
 | |
|   LOOKUP (GdipGetImageVerticalResolution);
 | |
|   LOOKUP (GdipLoadImageFromStream);
 | |
|   LOOKUP (GdipDeleteGraphics);
 | |
|   LOOKUP (GdipGetImageEncoders);
 | |
|   LOOKUP (GdipGetImageEncodersSize);
 | |
|   LOOKUP (GdipBitmapLockBits);
 | |
|   LOOKUP (GdipBitmapUnlockBits);
 | |
|   LOOKUP (GdipGetImagePixelFormat);
 | |
|   LOOKUP (GdipCloneBitmapAreaI);
 | |
| 
 | |
| #undef LOOKUP
 | |
| 
 | |
|   input.GdiplusVersion = 1;
 | |
|   input.DebugEventCallback = NULL;
 | |
|   input.SuppressBackgroundThread = input.SuppressExternalCodecs = FALSE;
 | |
|   
 | |
|   return (GdiplusStartup (&gdiplusToken, &input, NULL) == 0 ? TRUE : FALSE);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| GetEncoderClsid (const WCHAR *format, CLSID *pClsid)
 | |
| {
 | |
|   UINT num, size;
 | |
|   int j;
 | |
|   ImageCodecInfo *pImageCodecInfo;
 | |
|     
 | |
|   if (Ok != GdipGetImageEncodersSize (&num, &size))
 | |
|     return FALSE;
 | |
|     
 | |
|   pImageCodecInfo = (ImageCodecInfo *) g_malloc (size);
 | |
|     
 | |
|   if (Ok != GdipGetImageEncoders (num, size, pImageCodecInfo)) {
 | |
|     g_free (pImageCodecInfo);
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   for (j = 0; j < num; j++) {
 | |
|     if (wcscmp (pImageCodecInfo[j].MimeType, format) == 0) {
 | |
|       *pClsid = pImageCodecInfo[j].Clsid;
 | |
|       g_free (pImageCodecInfo);
 | |
|       return TRUE;
 | |
|     }
 | |
|   }
 | |
|  
 | |
|   g_free (pImageCodecInfo);
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static HGLOBAL
 | |
| gdip_buffer_to_hglobal (const gchar *buffer, size_t size, GError **error)
 | |
| {
 | |
|   HGLOBAL hg = NULL;
 | |
| 
 | |
|   hg = GlobalAlloc (GPTR, size);
 | |
| 
 | |
|   if (!hg) {
 | |
|     gdip_set_error_from_hresult (error, GDK_PIXBUF_ERROR_FAILED, GetLastError (), _("Could not allocate memory: %s"));
 | |
|     return NULL;
 | |
|   }
 | |
| 
 | |
|   CopyMemory (hg, buffer, size);
 | |
| 
 | |
|   return hg;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdip_save_bitmap_to_callback (GpBitmap *bitmap,
 | |
|                               const CLSID *format,
 | |
|                               const EncoderParameters *encoder_params,
 | |
|                               GdkPixbufSaveFunc save_func,
 | |
|                               gpointer user_data,
 | |
|                               GError **error)
 | |
| {
 | |
|   HRESULT hr;  
 | |
|   IStream *streamOut = NULL;
 | |
|   gboolean success = FALSE;
 | |
|   guint64 zero = 0;
 | |
|   GpStatus status;
 | |
| 
 | |
|   hr = CreateStreamOnHGlobal (NULL, TRUE, &streamOut);
 | |
|   if (!SUCCEEDED (hr)) {
 | |
|     gdip_set_error_from_hresult (error, GDK_PIXBUF_ERROR_FAILED, hr, _("Could not create stream: %s"));
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   status = GdipSaveImageToStream ((GpImage *)bitmap, streamOut, format, encoder_params);
 | |
|   if (Ok != status) {
 | |
|     gdip_set_error_from_gpstatus (error, GDK_PIXBUF_ERROR_FAILED, status);
 | |
|     IStream_Release (streamOut);
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   /* seek back to the beginning of the stream */
 | |
|   hr = IStream_Seek (streamOut, *(LARGE_INTEGER *)&zero, STREAM_SEEK_SET, NULL);
 | |
|   if (!SUCCEEDED (hr)) {
 | |
|     gdip_set_error_from_hresult (error, GDK_PIXBUF_ERROR_FAILED, hr, _("Could not seek stream: %s"));
 | |
|     IStream_Release (streamOut);
 | |
|     return FALSE;
 | |
|   }
 | |
|   
 | |
|   for (;;) {
 | |
|     char buffer[LOAD_BUFFER_SIZE];
 | |
|     ULONG nread;
 | |
|     
 | |
|     hr = IStream_Read (streamOut, buffer, sizeof(buffer), &nread);
 | |
|     if (!SUCCEEDED (hr))
 | |
|       {
 | |
|         gdip_set_error_from_hresult (error, GDK_PIXBUF_ERROR_FAILED, hr, _("Could not read from stream: %s"));
 | |
|         break;
 | |
|       }
 | |
|     else if (0 == nread) {
 | |
|       success = TRUE; /* EOF */
 | |
|       break;
 | |
|     }
 | |
|     else if (!(*save_func) (buffer, nread, error, user_data))
 | |
|       break;
 | |
|   }
 | |
|   
 | |
|   IStream_Release (streamOut);
 | |
|   
 | |
|   return success;
 | |
| }                     
 | |
| 
 | |
| static GpBitmap *
 | |
| gdip_pixbuf_to_bitmap (GdkPixbuf *pixbuf)
 | |
| {
 | |
|   GpBitmap *bitmap = NULL;
 | |
| 
 | |
|   int width, height, stride, n_channels;
 | |
|   guint8 *pixels;
 | |
| 
 | |
|   width = gdk_pixbuf_get_width (pixbuf);
 | |
|   height = gdk_pixbuf_get_height (pixbuf);
 | |
|   stride = gdk_pixbuf_get_rowstride (pixbuf);
 | |
|   n_channels = gdk_pixbuf_get_n_channels (pixbuf);
 | |
|   pixels = gdk_pixbuf_get_pixels (pixbuf);
 | |
| 
 | |
|   if (n_channels == 3 || n_channels == 4) {
 | |
|     /* rgbX. need to convert to argb. pass a null data to get an empty bitmap */
 | |
|     GdipCreateBitmapFromScan0 (width, height, 0, PixelFormat32bppARGB, NULL, &bitmap);
 | |
|     
 | |
|     if (bitmap) {
 | |
|       int x, y;
 | |
|       
 | |
|       for (y = 0; y < height; y++) {
 | |
|         for (x = 0; x < width; x++) {
 | |
|           ARGB p;
 | |
|           guint8 alpha;
 | |
|           guchar *base = pixels + (y * stride + (x * n_channels));
 | |
|           
 | |
|           if (n_channels == 4)
 | |
|             alpha = base[3];
 | |
|           else
 | |
|             alpha = 0xff;
 | |
|                   
 | |
|           if (alpha == 0) 
 | |
|             p = 0;
 | |
|           else {
 | |
|             guint8 red = base[0];
 | |
|             guint8 green = base[1];
 | |
|             guint8 blue = base[2];
 | |
|             
 | |
|             p = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
 | |
|           }
 | |
|           
 | |
|           GdipBitmapSetPixel (bitmap, x, y, p);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     g_warning ("Unsupported number of channels: %d\n", n_channels);
 | |
|   }
 | |
|   
 | |
|   return bitmap;
 | |
| }
 | |
| 
 | |
| static GpBitmap *
 | |
| gdip_buffer_to_bitmap (const gchar *buffer, size_t size, GError **error)
 | |
| {
 | |
|   HRESULT hr;
 | |
|   HGLOBAL hg = NULL;
 | |
|   GpBitmap *bitmap = NULL;
 | |
|   IStream *stream = NULL;
 | |
|   GpStatus status;
 | |
|   guint64 size64 = size;
 | |
| 
 | |
|   hg = gdip_buffer_to_hglobal (buffer, size, error);
 | |
| 
 | |
|   if (!hg)
 | |
|     return NULL;
 | |
| 
 | |
|   IStream_SetSize (stream, *(ULARGE_INTEGER *)&size64);
 | |
|   hr = CreateStreamOnHGlobal (hg, FALSE, (LPSTREAM *)&stream);
 | |
| 
 | |
|   if (!SUCCEEDED (hr)) {
 | |
|     gdip_set_error_from_hresult (error, GDK_PIXBUF_ERROR_FAILED, hr, _("Could not create stream: %s"));
 | |
|     GlobalFree (hg);
 | |
|     return NULL;
 | |
|   }
 | |
|   
 | |
|   status = GdipCreateBitmapFromStream (stream, &bitmap);
 | |
| 
 | |
|   if (Ok != status)
 | |
|     gdip_set_error_from_gpstatus (error, GDK_PIXBUF_ERROR_FAILED, status);
 | |
| 
 | |
|   IStream_Release (stream);
 | |
|   GlobalFree (hg);
 | |
| 
 | |
|   return bitmap;
 | |
| }
 | |
| 
 | |
| static GpImage *
 | |
| gdip_buffer_to_image (const gchar *buffer, size_t size, GError **error)
 | |
| {
 | |
|   HRESULT hr;
 | |
|   HGLOBAL hg = NULL;
 | |
|   GpImage *image = NULL;
 | |
|   IStream *stream = NULL;
 | |
|   GpStatus status;
 | |
|   guint64 size64 = size;
 | |
| 
 | |
|   hg = gdip_buffer_to_hglobal (buffer, size, error);
 | |
| 
 | |
|   if (!hg)
 | |
|     return NULL;
 | |
| 
 | |
|   hr = CreateStreamOnHGlobal (hg, FALSE, (LPSTREAM *)&stream);
 | |
| 
 | |
|   if (!SUCCEEDED (hr)) {
 | |
|     gdip_set_error_from_hresult (error, GDK_PIXBUF_ERROR_FAILED, hr, _("Could not create stream: %s"));
 | |
|     GlobalFree (hg);
 | |
|     return NULL;
 | |
|   }
 | |
|   
 | |
|   IStream_SetSize (stream, *(ULARGE_INTEGER *)&size64);
 | |
|   status = GdipLoadImageFromStream (stream, &image);
 | |
| 
 | |
|   if (Ok != status)
 | |
|     gdip_set_error_from_gpstatus (error, GDK_PIXBUF_ERROR_FAILED, status);
 | |
| 
 | |
|   IStream_Release (stream);
 | |
|   GlobalFree (hg);
 | |
| 
 | |
|   return image;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gdip_bitmap_get_size (GpBitmap *bitmap, guint *width, guint *height)
 | |
| {
 | |
|   if (bitmap == NULL || width == NULL || height == NULL)
 | |
|     return;
 | |
| 
 | |
|   *width = *height = 0;
 | |
| 
 | |
|   GdipGetImageWidth ((GpImage *) bitmap, width);
 | |
|   GdipGetImageHeight ((GpImage *) bitmap, height);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gdip_bitmap_get_has_alpha (GpBitmap *bitmap, gboolean *has_alpha)
 | |
| {
 | |
|   guint flags = 0;
 | |
| 
 | |
|   if (bitmap == NULL || has_alpha == NULL)
 | |
|     return;
 | |
| 
 | |
|   GdipGetImageFlags ((GpImage *) bitmap, &flags);
 | |
|   *has_alpha = (flags & ImageFlagsHasAlpha);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdip_bitmap_get_n_frames (GpBitmap *bitmap, guint *n_frames, gboolean timeDimension)
 | |
| {
 | |
|   if (bitmap == NULL || n_frames == NULL)
 | |
|     return FALSE;
 | |
| 
 | |
|   *n_frames = 1;
 | |
| 
 | |
|   return (Ok == GdipImageGetFrameCount ((GpImage *) bitmap, (timeDimension ? &FrameDimensionTime : &FrameDimensionPage), n_frames));
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdip_bitmap_select_frame (GpBitmap *bitmap, guint frame, gboolean timeDimension)
 | |
| {
 | |
|   if (bitmap == NULL)
 | |
|     return FALSE;
 | |
| 
 | |
|   return (Ok == GdipImageSelectActiveFrame ((GpImage *)bitmap, (timeDimension ? &FrameDimensionTime : &FrameDimensionPage), frame));
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdip_bitmap_get_property_as_string (GpBitmap *bitmap, guint propertyId, gchar **str)
 | |
| {
 | |
|   guint item_size;
 | |
|   gboolean success = FALSE;
 | |
| 
 | |
|   if (bitmap == NULL || str == NULL)
 | |
|     return FALSE;
 | |
| 
 | |
|   *str = 0;
 | |
| 
 | |
|   if (Ok == GdipGetPropertyItemSize ((GpImage *)bitmap, propertyId, &item_size)) {
 | |
|     PropertyItem *item;
 | |
|     
 | |
|     item = (PropertyItem *)g_try_malloc (item_size);
 | |
|     if (Ok == GdipGetPropertyItem ((GpImage *)bitmap, propertyId, item_size, item)) {
 | |
|       GString *gstr;
 | |
|       int i;
 | |
|       
 | |
|       gstr = g_string_new (NULL);
 | |
|       
 | |
|       success = TRUE;
 | |
|       switch (item->type) {
 | |
|       case PropertyTagTypeByte:
 | |
|         for (i = 0; i < item->length / sizeof(guint8); i++) {
 | |
|           guint8 *bytes = (guint8 *)item->value;
 | |
|           
 | |
|           if (gstr->len != 0)
 | |
|             g_string_append_c(gstr, ',');
 | |
|           g_string_append_printf (gstr, "%u", (guint32)bytes[i]);
 | |
|         }
 | |
|         break;
 | |
|         
 | |
|       case PropertyTagTypeASCII:
 | |
|         g_string_append_len (gstr, (const char *)item->value, item->length);
 | |
|         break;
 | |
|         
 | |
|       case PropertyTagTypeShort:
 | |
|         for (i = 0; i < item->length / sizeof(guint16); i++) {
 | |
|           guint16 *shorts = (guint16 *)item->value;
 | |
|           
 | |
|           if (gstr->len != 0)
 | |
|             g_string_append_c (gstr, ',');
 | |
|           g_string_append_printf (gstr, "%u", (guint32)shorts[i]);
 | |
|         }
 | |
|         break;
 | |
|         
 | |
|       case PropertyTagTypeLong:
 | |
|         for (i = 0; i < item->length / sizeof(guint32); i++) {
 | |
|           guint32 *longs = (guint32 *)item->value;
 | |
|           
 | |
|           if (gstr->len != 0)
 | |
|             g_string_append_c (gstr, ',');
 | |
|           g_string_append_printf (gstr, "%u", longs[i]);
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       case PropertyTagTypeSLONG:
 | |
|         for (i = 0; i < item->length / sizeof(guint32); i++) {
 | |
|           gint32 *longs = (gint32 *)item->value;
 | |
|           
 | |
|           if (gstr->len != 0)
 | |
|             g_string_append_c (gstr, ',');
 | |
|           g_string_append_printf (gstr, "%d", longs[i]);
 | |
|         }
 | |
|         break;
 | |
|         
 | |
|       default:
 | |
|         success = FALSE;
 | |
|         break;
 | |
|       }
 | |
|       
 | |
|       if (gstr->len > 0)
 | |
|         *str = g_string_free (gstr, FALSE);
 | |
|       else
 | |
|         g_string_free (gstr, TRUE);
 | |
|     }
 | |
|     
 | |
|     g_free (item);
 | |
|   }
 | |
|   
 | |
|   return success;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdip_bitmap_get_frame_delay (GpBitmap *bitmap, guint *delay)
 | |
| {
 | |
|   guint item_size;
 | |
|   gboolean success = FALSE;
 | |
| 
 | |
|   if (bitmap == NULL || delay == NULL)
 | |
|     return FALSE;
 | |
| 
 | |
|   *delay = 0;
 | |
| 
 | |
|   if (Ok == GdipGetPropertyItemSize ((GpImage *)bitmap, PropertyTagFrameDelay, &item_size)) {
 | |
|     PropertyItem *item;
 | |
|     
 | |
|     item = (PropertyItem *)g_try_malloc (item_size);
 | |
|     if (Ok == GdipGetPropertyItem ((GpImage *)bitmap, PropertyTagFrameDelay, item_size, item)) {
 | |
|       /* PropertyTagFrameDelay. Time delay, in hundredths of a second, between two frames in an animated GIF image. */
 | |
|       *delay = *((long *)item->value);
 | |
|       success = TRUE;
 | |
|     }
 | |
|     
 | |
|     g_free (item);
 | |
|   }
 | |
|   
 | |
|   return success;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdip_bitmap_get_n_loops (GpBitmap *bitmap, guint *loops)
 | |
| {
 | |
|   guint item_size;
 | |
|   gboolean success = FALSE;
 | |
| 
 | |
|   if (bitmap == NULL || loops == NULL)
 | |
|     return FALSE;
 | |
| 
 | |
|   *loops = 1;
 | |
| 
 | |
|   /* PropertyTagLoopCount. 0 == infinitely */
 | |
|   if (Ok == GdipGetPropertyItemSize ((GpImage *)bitmap, PropertyTagLoopCount, &item_size)) {
 | |
|     PropertyItem *item;
 | |
|     
 | |
|     item = (PropertyItem *)g_try_malloc (item_size);
 | |
|     if (Ok == GdipGetPropertyItem ((GpImage *)bitmap, PropertyTagLoopCount, item_size, item)) {
 | |
|       *loops = *((short *)item->value);
 | |
|       success = TRUE;
 | |
|     }
 | |
|     
 | |
|     g_free (item);
 | |
|   }
 | |
|   
 | |
|   return success;
 | |
| }
 | |
| 
 | |
| /*************************************************************************/
 | |
| /*************************************************************************/
 | |
| 
 | |
| struct _GdipContext {
 | |
|   GdkPixbufModuleUpdatedFunc  updated_func;
 | |
|   GdkPixbufModulePreparedFunc prepared_func;
 | |
|   GdkPixbufModuleSizeFunc     size_func;
 | |
| 
 | |
|   gpointer                    user_data;
 | |
| 
 | |
|   GByteArray                 *buffer;
 | |
| };
 | |
| typedef struct _GdipContext GdipContext;
 | |
| 
 | |
| static void
 | |
| destroy_gdipcontext (GdipContext *context)
 | |
| {
 | |
|   if (context != NULL) {
 | |
|     g_byte_array_free (context->buffer, TRUE);
 | |
|     g_free (context);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void
 | |
| emit_updated (GdipContext *context, GdkPixbuf *pixbuf)
 | |
| {
 | |
|   if (context->updated_func)
 | |
|     (*context->updated_func) (pixbuf,
 | |
|                               0, 0,
 | |
|                               gdk_pixbuf_get_width (pixbuf),
 | |
|                               gdk_pixbuf_get_height (pixbuf),
 | |
|                               context->user_data);
 | |
| }
 | |
| 
 | |
| static void
 | |
| emit_prepared (GdipContext *context, GdkPixbuf *pixbuf, GdkPixbufAnimation *anim)
 | |
| {
 | |
|   if (context->prepared_func)
 | |
|     (*context->prepared_func) (pixbuf, anim, context->user_data);
 | |
| }
 | |
| 
 | |
| static gpointer
 | |
| gdk_pixbuf__gdip_image_begin_load (GdkPixbufModuleSizeFunc size_func,
 | |
|                                    GdkPixbufModulePreparedFunc prepared_func,
 | |
|                                    GdkPixbufModuleUpdatedFunc  updated_func,
 | |
|                                    gpointer user_data,
 | |
|                                    GError **error)
 | |
| {
 | |
|   GdipContext *context = g_new0 (GdipContext, 1);
 | |
| 
 | |
|   context->size_func     = size_func;
 | |
|   context->prepared_func = prepared_func;
 | |
|   context->updated_func  = updated_func;
 | |
|   context->user_data     = user_data;
 | |
|   context->buffer        = g_byte_array_new ();
 | |
| 
 | |
|   return context;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdk_pixbuf__gdip_image_load_increment (gpointer data,
 | |
|                                        const guchar *buf, guint size,
 | |
|                                        GError **error)
 | |
| {
 | |
|   GdipContext *context = (GdipContext *)data;
 | |
|   GByteArray *image_buffer = context->buffer;
 | |
| 
 | |
|   g_byte_array_append (image_buffer, (guint8 *)buf, size);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static GdkPixbuf *
 | |
| gdip_bitmap_to_pixbuf (GpBitmap *bitmap, GError **error)
 | |
| {
 | |
|   GdkPixbuf *pixbuf = NULL;
 | |
|   guchar *cursor = NULL;
 | |
|   gint rowstride;
 | |
|   gboolean has_alpha = FALSE;
 | |
|   gint n_channels = 0;
 | |
|   gchar *option;
 | |
| 
 | |
|   guint width = 0, height = 0, x, y;
 | |
| 
 | |
|   gdip_bitmap_get_size (bitmap, &width, &height);
 | |
|   gdip_bitmap_get_has_alpha (bitmap, &has_alpha);
 | |
| 
 | |
|   pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, has_alpha, 8, width, height);
 | |
| 
 | |
|   if (!pixbuf) {
 | |
|     g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Couldn't load bitmap"));
 | |
|     return NULL;
 | |
|   }
 | |
| 
 | |
|   rowstride = gdk_pixbuf_get_rowstride (pixbuf);
 | |
|   cursor = gdk_pixbuf_get_pixels (pixbuf);
 | |
|   n_channels = gdk_pixbuf_get_n_channels (pixbuf);
 | |
| 
 | |
|   for (y = 0; y < height; y++) {
 | |
|     for (x = 0; x < width; x++) {
 | |
|       ARGB pixel;
 | |
|       GpStatus status;
 | |
|       guchar *b = cursor + (y * rowstride + (x * n_channels));
 | |
|       
 | |
|       if (Ok != (status = GdipBitmapGetPixel (bitmap, x, y, &pixel))) {
 | |
|         gdip_set_error_from_gpstatus (error, GDK_PIXBUF_ERROR_FAILED, status);
 | |
|         g_object_unref (pixbuf);
 | |
|         return NULL;
 | |
|       }
 | |
|       
 | |
|       b[0] = (pixel & 0xff0000) >> 16;
 | |
|       b[1] = (pixel & 0x00ff00) >> 8;
 | |
|       b[2] = (pixel & 0x0000ff) >> 0;
 | |
|       
 | |
|       if (has_alpha)      
 | |
|         b[3] = (pixel & 0xff000000) >> 24;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (gdip_bitmap_get_property_as_string (bitmap, PropertyTagOrientation, &option)) {
 | |
|     gdk_pixbuf_set_option (pixbuf, "orientation", option);
 | |
|     g_free (option);
 | |
|   }
 | |
| 
 | |
|   if (gdip_bitmap_get_property_as_string (bitmap, PropertyTagArtist, &option)) {
 | |
|     gdk_pixbuf_set_option (pixbuf, "Author", option);
 | |
|     g_free (option);
 | |
|   }
 | |
| 
 | |
|   if (gdip_bitmap_get_property_as_string (bitmap, PropertyTagImageTitle, &option)) {
 | |
|     gdk_pixbuf_set_option (pixbuf, "Title", option);
 | |
|     g_free (option);
 | |
|   }
 | |
| 
 | |
|   return pixbuf;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| stop_load (GpBitmap *bitmap, GdipContext *context, GError **error)
 | |
| {
 | |
|   guint       n_frames = 1, i;
 | |
|   GdkPixbufGdipAnim *animation = NULL;
 | |
| 
 | |
|   gdip_bitmap_get_n_frames (bitmap, &n_frames, TRUE);
 | |
| 
 | |
|   for (i = 0; i < n_frames; i++) {
 | |
|     GdkPixbuf *pixbuf = NULL;
 | |
|     GdkPixbufFrame *frame;
 | |
|     guint frame_delay = 0;
 | |
| 
 | |
|     gdip_bitmap_select_frame (bitmap, i, TRUE);
 | |
|     
 | |
|     pixbuf = gdip_bitmap_to_pixbuf (bitmap, error);
 | |
|     
 | |
|     if (!pixbuf) {
 | |
|       if (animation != NULL)
 | |
|         g_object_unref (G_OBJECT (animation));
 | |
| 
 | |
|       GdipDisposeImage ((GpImage *)bitmap);
 | |
|       destroy_gdipcontext (context);
 | |
|       return FALSE;
 | |
|     }
 | |
|     
 | |
|     if (animation == NULL) {
 | |
|       guint n_loops = 1;
 | |
| 
 | |
|       animation = g_object_new (GDK_TYPE_PIXBUF_GDIP_ANIM, NULL);
 | |
|       gdip_bitmap_get_n_loops (bitmap, &n_loops);
 | |
|       animation->loop = n_loops;
 | |
|     }
 | |
| 
 | |
|     frame = g_new (GdkPixbufFrame, 1);
 | |
|     frame->pixbuf = pixbuf;
 | |
| 
 | |
|     gdip_bitmap_get_frame_delay (bitmap, &frame_delay);
 | |
|   
 | |
|     animation->n_frames++;
 | |
|     animation->frames = g_list_append (animation->frames, frame);
 | |
| 
 | |
|     animation->width = gdk_pixbuf_get_width (pixbuf);
 | |
|     animation->height = gdk_pixbuf_get_height (pixbuf);
 | |
| 
 | |
|     /* GIF delay is in hundredths, we want thousandths */
 | |
|     frame->delay_time = frame_delay * 10;
 | |
|     frame->elapsed = animation->total_time;
 | |
|     
 | |
|     /* Some GIFs apparently have delay time of 0,
 | |
|      * that crashes everything so set it to "fast".
 | |
|      * Also, timeouts less than 20 or so just lock up
 | |
|      * the app or make the animation choppy, so fix them.
 | |
|      */
 | |
|     if (frame->delay_time < 20)
 | |
|       frame->delay_time = 20; /* 20 = "fast" */
 | |
| 
 | |
|     animation->total_time += frame->delay_time;
 | |
| 
 | |
|     if (i == 0)
 | |
|       emit_prepared (context, pixbuf, GDK_PIXBUF_ANIMATION (animation));
 | |
| 
 | |
|     emit_updated (context, pixbuf);
 | |
|   }
 | |
| 
 | |
|   if (animation != NULL)
 | |
|     g_object_unref (G_OBJECT (animation));
 | |
| 
 | |
|   GdipDisposeImage ((GpImage *)bitmap);
 | |
|   destroy_gdipcontext (context);
 | |
|   
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdk_pixbuf__gdip_image_stop_load (gpointer data, GError **error)
 | |
| {
 | |
|   GdipContext *context = (GdipContext *)data;
 | |
|   GpBitmap    *bitmap = NULL;
 | |
|   GByteArray *image_buffer = context->buffer;
 | |
| 
 | |
|   bitmap = gdip_buffer_to_bitmap ((gchar *)image_buffer->data, image_buffer->len, error);
 | |
| 
 | |
|   if (!bitmap) {
 | |
|     destroy_gdipcontext (context);
 | |
|     g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, _("Couldn't load bitmap"));
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   return stop_load (bitmap, context, error);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gdk_pixbuf__gdip_image_stop_vector_load (gpointer data, GError **error)
 | |
| {
 | |
|   GdipContext *context = (GdipContext *)data;
 | |
|   GByteArray *image_buffer = context->buffer;
 | |
| 
 | |
|   GpImage *metafile;
 | |
|   GpGraphics *graphics;
 | |
|   GpBitmap *bitmap;
 | |
|   GpStatus status;
 | |
|   float metafile_xres, metafile_yres;
 | |
|   guint width, height;
 | |
| 
 | |
|   metafile = gdip_buffer_to_image ((gchar *)image_buffer->data, image_buffer->len, error);
 | |
|   if (!metafile) {
 | |
|     destroy_gdipcontext (context);
 | |
|     g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, _("Couldn't load metafile"));
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   GdipGetImageWidth (metafile, &width);
 | |
|   GdipGetImageHeight (metafile, &height);
 | |
| 
 | |
|   status = GdipCreateBitmapFromScan0 (width, height, 0, PixelFormat32bppARGB, NULL, &bitmap);
 | |
|   if (Ok != status) {
 | |
|     gdip_set_error_from_gpstatus (error, GDK_PIXBUF_ERROR_FAILED, status);
 | |
|     GdipDisposeImage (metafile);
 | |
|     
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   GdipGetImageHorizontalResolution (metafile, &metafile_xres);
 | |
|   GdipGetImageVerticalResolution (metafile, &metafile_yres);
 | |
|   GdipBitmapSetResolution (bitmap, metafile_xres, metafile_yres);
 | |
| 
 | |
|   status = GdipGetImageGraphicsContext ((GpImage *)bitmap, &graphics);
 | |
|   if (Ok != status) {
 | |
|     gdip_set_error_from_gpstatus (error, GDK_PIXBUF_ERROR_FAILED, status);
 | |
|     GdipDisposeImage ((GpImage *)bitmap);
 | |
|     GdipDisposeImage (metafile);
 | |
|     
 | |
|     return FALSE;
 | |
|   }
 | |
|   
 | |
|   /* gotta clear the bitmap */
 | |
|   GdipGraphicsClear (graphics, 0xffffffff);
 | |
|   
 | |
|   status = GdipDrawImageI (graphics, metafile, 0, 0);
 | |
|   if (Ok != status) {
 | |
|     gdip_set_error_from_gpstatus (error, GDK_PIXBUF_ERROR_FAILED, status);
 | |
|     GdipDeleteGraphics (graphics);
 | |
|     GdipDisposeImage ((GpImage *)bitmap);
 | |
|     GdipDisposeImage (metafile);
 | |
|     
 | |
|     return FALSE;
 | |
|   }
 | |
|   
 | |
|   GdipFlush (graphics, 1);
 | |
|   
 | |
|   GdipDeleteGraphics (graphics);
 | |
|   GdipDisposeImage (metafile);
 | |
| 
 | |
|   return stop_load (bitmap, context, error);
 | |
| }
 | |
| 
 | |
| static void 
 | |
| gdip_animation_prepare (GdkPixbuf *pixbuf,
 | |
|                         GdkPixbufAnimation *animation,
 | |
|                         gpointer user_data)
 | |
| {
 | |
|   GdkPixbufAnimation **anim;
 | |
| 
 | |
|   anim = (GdkPixbufAnimation **)user_data;
 | |
| 
 | |
|   /* save a reference to the animation */
 | |
|   g_object_ref (animation);
 | |
|   *anim = animation;
 | |
| }
 | |
| 
 | |
| static GdkPixbufAnimation *
 | |
| gdk_pixbuf__gdip_image_load_animation (FILE *file,
 | |
|                                        GError **error)
 | |
| {
 | |
|   GdkPixbufAnimation *animation = NULL;
 | |
| 
 | |
|   gpointer context;
 | |
|   char buffer[LOAD_BUFFER_SIZE];
 | |
|   size_t length;
 | |
| 
 | |
|   context = gdk_pixbuf__gdip_image_begin_load (NULL, gdip_animation_prepare, NULL, &animation, error);
 | |
| 
 | |
|   while (!feof (file) && !ferror (file)) {
 | |
|     length = fread (buffer, 1, sizeof (buffer), file);
 | |
|     if (length > 0) {
 | |
|       if (!gdk_pixbuf__gdip_image_load_increment (context, buffer, length, error)) {
 | |
|         gdk_pixbuf__gdip_image_stop_load (context, NULL);
 | |
| 
 | |
|         if (animation)
 | |
|           g_object_unref (animation);
 | |
| 
 | |
|         return NULL;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!gdk_pixbuf__gdip_image_stop_load(context, error)) {
 | |
|     if (animation)
 | |
|       g_object_unref (animation);
 | |
|     
 | |
|     return NULL;
 | |
|   }
 | |
| 
 | |
|   return animation;
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| gdip_save_to_file_callback (const gchar *buf,
 | |
|                             gsize        count,
 | |
|                             GError     **error,
 | |
|                             gpointer     data)
 | |
| {
 | |
|   FILE *filehandle = data;
 | |
|   gsize n;
 | |
|   
 | |
|   n = fwrite (buf, 1, count, filehandle);
 | |
|   if (n != count) {
 | |
|     gint save_errno = errno;
 | |
|     g_set_error (error,
 | |
|                  G_FILE_ERROR,
 | |
|                  g_file_error_from_errno (save_errno),
 | |
|                  _("Error writing to image file: %s"),
 | |
|                  g_strerror (save_errno));
 | |
|     return FALSE;
 | |
|   }
 | |
|   
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| void
 | |
| gdip_fill_vtable (GdkPixbufModule *module)
 | |
| {
 | |
|   if (gdip_init ()) {
 | |
|     module->begin_load     = gdk_pixbuf__gdip_image_begin_load;
 | |
|     module->stop_load      = gdk_pixbuf__gdip_image_stop_load;
 | |
|     module->load_increment = gdk_pixbuf__gdip_image_load_increment;
 | |
|     
 | |
|     /* this is the only way to get gtk_image_new_from_file() to load animations. it regrettably
 | |
|        does not use the GdkPixbufLoader interface. */
 | |
|     module->load_animation = gdk_pixbuf__gdip_image_load_animation;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| gdip_fill_vector_vtable (GdkPixbufModule *module)
 | |
| {
 | |
|   if (gdip_init ()) {
 | |
|     module->begin_load     = gdk_pixbuf__gdip_image_begin_load;
 | |
|     module->stop_load      = gdk_pixbuf__gdip_image_stop_vector_load;
 | |
|     module->load_increment = gdk_pixbuf__gdip_image_load_increment;
 | |
|   }
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| gdip_save_pixbuf (GdkPixbuf *pixbuf,
 | |
|                   const WCHAR *format,
 | |
|                   const EncoderParameters *encoder_params,
 | |
|                   GdkPixbufSaveFunc save_func,
 | |
|                   gpointer user_data,
 | |
|                   GError **error)
 | |
| {
 | |
|   GpBitmap *image;
 | |
|   CLSID clsid;
 | |
|   gboolean success;
 | |
| 
 | |
|   if (!GetEncoderClsid (format, &clsid)) {
 | |
|     g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, _("Unsupported image format for GDI+"));
 | |
|     return FALSE;
 | |
|   }
 | |
|   
 | |
|   image = gdip_pixbuf_to_bitmap (pixbuf);
 | |
| 
 | |
|   if (image == NULL) {
 | |
|     g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, _("Couldn't save"));
 | |
|     return FALSE;
 | |
|   }
 | |
|   
 | |
|   success = gdip_save_bitmap_to_callback (image, &clsid, encoder_params, save_func, user_data, error);
 | |
| 
 | |
|   GdipDisposeImage ((GpImage *)image);
 | |
| 
 | |
|   return success;
 | |
| }
 |