diff options
author | Tor Lillqvist <tml@novell.com> | 2008-03-20 23:24:45 +0000 |
---|---|---|
committer | Tor Lillqvist <tml@src.gnome.org> | 2008-03-20 23:24:45 +0000 |
commit | 6d15bbbe1da2a30a2bf4b519cd03bf3bc80ccfaf (patch) | |
tree | 7f865e3e03fc46190ca21516069defda81150860 /gdk-pixbuf/io-gdip-utils.c | |
parent | 84fa2c6194c90ddaa05c353ab75fe89750f47677 (diff) | |
download | gtk+-6d15bbbe1da2a30a2bf4b519cd03bf3bc80ccfaf.tar.gz |
Import the GDI+ gdk-pixbuf loaders for Windows by Dominic Lachowicz and
2008-03-21 Tor Lillqvist <tml@novell.com>
Import the GDI+ gdk-pixbuf loaders for Windows by Dominic
Lachowicz and Alberto Ruiz into the GTK+ tree, from the
gdip-pixbuf-loader module.
* configure.in: Add switch --disable-gdiplus-loaders that disables
building of the GDI+ loaders.
When including loaders in the gdk-pixbuf library, either build in
abll the GDI+ ones or none of them. Use just -DINCLUDE_gdiplus in
$INCLUDED_LOADER_DEFINE to signal building them in.
Add Automake conditionals BUILD_GDIPLUS_LOADERS to indicate
whether the GDI+ loaders should be built and INCLUDE_GDIPLUS to
indicate whether they should be built-in.
For the rest of the changes, see gdk-pixbuf/ChangeLog.
In gdk-pixbuf:
* Makefile.am: Add the bits and pieces for the GDI+ loaders. When
building GDI+ loaders don't build the traditional ones for the
same formats. Always build the traditional PNG loader, though, as
it isn't possible to read and write PNG tEXt chunks through GDI+,
and GIMP at least needs that functionality in the gdk-pixbuf PNG
loader.
Either build all the GDI+ loaders (except the PNG one) into
libgdk-pixbuf, or build them all as DLLs. I don't see any reason
to enable cherry-picking among them whether to build in or not.
* io-gdip-animation.c
* io-gdip-animation.h
* io-gdip-bmp.c
* io-gdip-emf.c
* io-gdip-gif.c
* io-gdip-ico.c
* io-gdip-jpeg.c
* io-gdip-native.h
* io-gdip-png.c
* io-gdip-propertytags.h
* io-gdip-tiff.c
* io-gdip-utils.c
* io-gdip-utils.h
* io-gdip-wmf.c: New files. Note that io-gdip-png.c is not
currently used.
* gdk-pixbuf-io.c: Add the bits and pieces for built-in GDI+
loaders.
svn path=/trunk/; revision=19914
Diffstat (limited to 'gdk-pixbuf/io-gdip-utils.c')
-rw-r--r-- | gdk-pixbuf/io-gdip-utils.c | 996 |
1 files changed, 996 insertions, 0 deletions
diff --git a/gdk-pixbuf/io-gdip-utils.c b/gdk-pixbuf/io-gdip-utils.c new file mode 100644 index 0000000000..392c3bfc6a --- /dev/null +++ b/gdk-pixbuf/io-gdip-utils.c @@ -0,0 +1,996 @@ +/* -*- 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; + +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 (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); + +#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; + + 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; + } + + 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; + + 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; + } + + 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) +{ + 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) + 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; + guchar *b = cursor + (y * rowstride + (x * n_channels)); + + if (Ok != GdipBitmapGetPixel (bitmap, x, y, &pixel)) { + 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); + + if (!pixbuf) { + if (animation != NULL) + g_object_unref (G_OBJECT (animation)); + + destroy_gdipcontext (context); + g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Couldn't create pixbuf")); + 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)); + + 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 (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 (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 (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 (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; +} |