Blob Blame History Raw
/*
|  Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
|  Part of the gtkpod project.
|
|  URL: http://www.gtkpod.org/
|  URL: http://gtkpod.sourceforge.net/
|
|  The code contained in this file 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.1 of the License, or (at your option) any later version.
|
|  This file 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 details.
|
|  You should have received a copy of the GNU Lesser General Public
|  License along with this code; if not, write to the Free Software
|  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
|
|  iTunes and iPod are trademarks of Apple
|
|  This product is not supported/written/published by Apple!
|
|  $Id$
*/

#include <config.h>

#include "itdb_device.h"
#include "itdb_private.h"
#include "itdb_thumb.h"
#include "db-image-parser.h"
#include "itdb_endianness.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#if HAVE_GDKPIXBUF
#include <gdk-pixbuf/gdk-pixbuf.h>
#endif
#include <glib/gi18n-lib.h>
#include <glib/gstdio.h>
#include <stdlib.h>

/**
 * itdb_artwork_new:
 *
 * Creates a new #Itdb_Artwork
 *
 * Returns: a new #Itdb_Artwork to be freed with itdb_artwork_free() when
 * no longer needed
 *
 * Since: 0.3.0
 */
Itdb_Artwork *itdb_artwork_new (void)
{
    Itdb_Artwork *artwork = g_new0 (Itdb_Artwork, 1);
    return artwork;
}

/**
 * itdb_artwork_free:
 * @artwork: an #Itdb_Artwork
 *
 * Frees memory used by @artwork
 *
 * Since: 0.3.0
 */
void itdb_artwork_free (Itdb_Artwork *artwork)
{
    g_return_if_fail (artwork);
    itdb_artwork_remove_thumbnails (artwork);
    if (artwork->userdata && artwork->userdata_destroy)
	(*artwork->userdata_destroy) (artwork->userdata);
    g_free (artwork);
}

/**
 * itdb_artwork_duplicate:
 * @artwork: an #Itdb_Artwork
 *
 * Duplicates @artwork
 *
 * Returns: a new copy of @artwork
 *
 * Since: 0.3.0
 */
Itdb_Artwork *itdb_artwork_duplicate (Itdb_Artwork *artwork)
{
    Itdb_Artwork *dup;
    g_return_val_if_fail (artwork, NULL);

    dup = g_new0 (Itdb_Artwork, 1);

    memcpy (dup, artwork, sizeof (Itdb_Artwork));

    if (artwork->thumbnail != NULL) {
        dup->thumbnail = itdb_thumb_duplicate (artwork->thumbnail);
    }

    return dup;
}

/**
 * itdb_artwork_remove_thumbnails:
 * @artwork: an #Itdb_Artwork
 *
 * Removes all thumbnails from @artwork
 *
 * Since: 0.3.0
 */
void
itdb_artwork_remove_thumbnails (Itdb_Artwork *artwork)
{
    g_return_if_fail (artwork);

    if (artwork->thumbnail != NULL) {
        itdb_thumb_free (artwork->thumbnail);
    }
    artwork->thumbnail = NULL;
    artwork->artwork_size = 0;
    artwork->id = 0;
}

/**
 * itdb_artwork_set_thumbnail
 * @artwork:    an #Itdb_Artwork
 * @filename:   image file to use to create the thumbnail
 * @rotation:   angle by which the image should be rotated
 *              counterclockwise. Valid values are 0, 90, 180 and 270.
 * @error:      return location for a #GError or NULL
 *
 * Appends a thumbnail of type @type to existing thumbnails in @artwork. No
 * data is read from @filename yet, the file will be when @artwork is saved to
 * disk. @filename must still exist when that happens.
 *
 * For the rotation angle you can also use the gdk constants
 * %GDK_PIXBUF_ROTATE_NONE, %GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE,
 * %GDK_PIXBUF_ROTATE_UPSIDEDOWN, AND %GDK_PIXBUF_ROTATE_CLOCKWISE.
 *
 * Returns: TRUE if the thumbnail could be successfully added, FALSE
 * otherwise. @error is set appropriately.
 *
 * Since: 0.7.0
 */
gboolean
itdb_artwork_set_thumbnail (Itdb_Artwork *artwork,
			    const gchar *filename,
			    gint rotation,
			    GError **error)
{
#ifdef HAVE_GDKPIXBUF
/* This operation doesn't make sense when we can't save thumbnail files */
    struct stat statbuf;
    Itdb_Thumb *thumb;

    g_return_val_if_fail (artwork, FALSE);
    g_return_val_if_fail (filename, FALSE);

    if (g_stat  (filename, &statbuf) != 0) {
	g_set_error (error, 0, -1,
		     _("Could not access file '%s'."),
		     filename);
	return FALSE;
    }

    artwork->artwork_size  = statbuf.st_size;
    artwork->creation_date = statbuf.st_mtime;

    thumb = itdb_thumb_new_from_file (filename);
    itdb_thumb_set_rotation (thumb, rotation);
    if (artwork->thumbnail != NULL) {
        itdb_thumb_free (artwork->thumbnail);
    }
    artwork->thumbnail = thumb;

    return TRUE;
#else
    g_set_error (error, 0, -1,
		 _("Artwork support not compiled into libgpod."));
    return FALSE;
#endif
}

/**
 * itdb_artwork_set_thumbnail_from_pixbuf
 * @artwork:    an #Itdb_Artwork
 * @pixbuf:     #GdkPixbuf to use to create the thumbnail
 * @rotation:   angle by which the image should be rotated
 *              counterclockwise. Valid values are 0, 90, 180 and 270.
 * @error:      return location for a #GError or NULL
 *
 * Set a thumbnail in @artwork. No data is generated from @pixbuf yet, it will
 * be done when @artwork is saved to disk. @pixbuf is ref'ed by this function,
 * but is not copied, so it should not be modified before the database is saved.
 *
 * For the rotation angle you can also use the gdk constants
 * %GDK_PIXBUF_ROTATE_NONE, %GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE,
 * %GDK_PIXBUF_ROTATE_UPSIDEDOWN, AND %GDK_PIXBUF_ROTATE_CLOCKWISE.
 *
 * Returns: TRUE if the thumbnail could be successfully added, FALSE
 * otherwise. @error is set appropriately.
 *
 * Since: 0.7.0
 */
gboolean
itdb_artwork_set_thumbnail_from_pixbuf (Itdb_Artwork *artwork,
                                        gpointer pixbuf,
                                        gint rotation,
                                        GError **error)
{
#ifdef HAVE_GDKPIXBUF
/* This operation doesn't make sense when we can't save thumbnail files */
    Itdb_Thumb *thumb;
    GTimeVal time;
    gint rowstride;
    gint height;

    g_return_val_if_fail (artwork, FALSE);
    g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), FALSE);

    g_get_current_time (&time);
    g_object_get (G_OBJECT (pixbuf),
                  "height", &height,
                  "rowstride", &rowstride,
                  NULL);
    artwork->artwork_size  = rowstride * height;
    artwork->creation_date = time.tv_sec;

    thumb = itdb_thumb_new_from_pixbuf (pixbuf);
    itdb_thumb_set_rotation (thumb, rotation);
    if (artwork->thumbnail != NULL) {
        itdb_thumb_free (artwork->thumbnail);
    }
    artwork->thumbnail = thumb;

    return TRUE;
#else
    g_set_error (error, 0, -1,
		 _("Artwork support not compiled into libgpod."));
    return FALSE;
#endif
}

/**
 * itdb_artwork_set_thumbnail_from_data
 * @artwork:        an #Itdb_Artwork
 * @image_data:     data used to create the thumbnail (the raw contents of
 *                  an image file)
 * @image_data_len: length of above data block
 * @rotation:       angle by which the image should be rotated
 *                  counterclockwise. Valid values are 0, 90, 180 and 270.
 * @error:          return location for a #GError or NULL
 *
 * Set a thumbnail in @artwork. No data is processed yet. This will be done when
 * @artwork is saved to disk.
 *
 * For the rotation angle you can also use the gdk constants
 * %GDK_PIXBUF_ROTATE_NONE, %GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE,
 * %GDK_PIXBUF_ROTATE_UPSIDEDOWN, AND %GDK_PIXBUF_ROTATE_CLOCKWISE.
 *
 * Returns: TRUE if the thumbnail could be successfully added, FALSE
 * otherwise. @error is set appropriately.
 *
 * Since: 0.7.0
 */
gboolean
itdb_artwork_set_thumbnail_from_data (Itdb_Artwork *artwork,
				      const guchar *image_data,
				      gsize image_data_len,
				      gint rotation,
				      GError **error)
{
#ifdef HAVE_GDKPIXBUF
/* This operation doesn't make sense when we can't save thumbnail files */
    Itdb_Thumb *thumb;
    GTimeVal time;

    g_return_val_if_fail (artwork, FALSE);
    g_return_val_if_fail (image_data, FALSE);

    g_get_current_time (&time);

    artwork->artwork_size  = image_data_len;
    artwork->creation_date = time.tv_sec;

    thumb = itdb_thumb_new_from_data (image_data, image_data_len);
    itdb_thumb_set_rotation (thumb, rotation);
    if (artwork->thumbnail != NULL) {
        itdb_thumb_free (artwork->thumbnail);
    }
    artwork->thumbnail = thumb;

    return TRUE;
#else
    g_set_error (error, 0, -1,
		 _("Artwork support not compiled into libgpod."));
    return FALSE;
#endif
}

#if HAVE_GDKPIXBUF
static guchar *
unpack_RGB_565 (guint16 *pixels, guint bytes_len, guint byte_order)
{
	guchar *result;
	guint i;

	g_return_val_if_fail (bytes_len < 2*(G_MAXUINT/3), NULL);

	result = g_malloc ((bytes_len/2) * 3);

	for (i = 0; i < bytes_len/2; i++) {
		guint16 cur_pixel;
		/* FIXME: endianness */
		cur_pixel = get_gint16 (pixels[i], byte_order);
		/* Unpack pixels */
		result[3*i] = (cur_pixel & RED_MASK_565) >> RED_SHIFT_565;
		result[3*i+1] = (cur_pixel & GREEN_MASK_565) >> GREEN_SHIFT_565;
		result[3*i+2] = (cur_pixel & BLUE_MASK_565) >> BLUE_SHIFT_565;

		/* Normalize color values so that they use a [0..255] range */
		result[3*i] <<= (8 - RED_BITS_565);
		result[3*i+1] <<= (8 - GREEN_BITS_565);
		result[3*i+2] <<= (8 - BLUE_BITS_565);
	}

	return result;
}

static guchar *
unpack_RGB_555 (guint16 *pixels, guint bytes_len, guint byte_order)
{
	guchar *result;
	guint i;

	g_return_val_if_fail (bytes_len < 2*(G_MAXUINT/3), NULL);

	result = g_malloc ((bytes_len/2) * 3);

	for (i = 0; i < bytes_len/2; i++) {
		guint16 cur_pixel;
		/* FIXME: endianness */
		cur_pixel = get_gint16 (pixels[i], byte_order);
		/* Unpack pixels */
		result[3*i] = (cur_pixel & RED_MASK_555) >> RED_SHIFT_555;
		result[3*i+1] = (cur_pixel & GREEN_MASK_555) >> GREEN_SHIFT_555;
		result[3*i+2] = (cur_pixel & BLUE_MASK_555) >> BLUE_SHIFT_555;

		/* Normalize color values so that they use a [0..255] range */
		result[3*i] <<= (8 - RED_BITS_555);
		result[3*i+1] <<= (8 - GREEN_BITS_555);
		result[3*i+2] <<= (8 - BLUE_BITS_555);
	}

	return result;
}

static guchar *
unpack_RGB_888 (guint16 *pixels, guint bytes_len, guint byte_order)
{
	guchar *result;
	guint i;
	guint32 *pixels32;

	result = g_malloc ((bytes_len/4) * 3);

	pixels32 = (guint32 *)pixels;

	for (i = 0; i < bytes_len/4; i++) {
		guint32 cur_pixel;
		/* FIXME: endianness */
		cur_pixel = get_gint32 (pixels32[i], byte_order);
		/* Unpack pixels */
		result[3*i] = (cur_pixel & RED_MASK_888) >> RED_SHIFT_888;
		result[3*i+1] = (cur_pixel & GREEN_MASK_888) >> GREEN_SHIFT_888;
		result[3*i+2] = (cur_pixel & BLUE_MASK_888) >> BLUE_SHIFT_888;

		/* Normalize color values so that they use a [0..255] range */
		/* (not necessary for 888 encoding) */
/* 		result[3*i] <<= (8 - RED_BITS_888); */
/* 		result[3*i+1] <<= (8 - GREEN_BITS_888); */
/* 		result[3*i+2] <<= (8 - BLUE_BITS_888); */
	}

	return result;
}


static guint16 *rearrange_pixels (guint16 *pixels_s, guint16 *pixels_d,
				  gint width, gint height, gint row_stride)
{
    g_return_val_if_fail (width == height, pixels_d);

    if (pixels_d == NULL)
    {
	pixels_d = g_malloc0 (sizeof (guint16)*width*height);
    }

    if (width == 1)
    {
	*pixels_d = *pixels_s;
    }
    else
    {
	rearrange_pixels (pixels_s + 0,
			  pixels_d + 0 + 0,
			  width/2, height/2,
			  row_stride);
	rearrange_pixels (pixels_s + (width/2)*(height/2),
			  pixels_d + (height/2)*row_stride + 0,
			  width/2, height/2,
			  row_stride);
	rearrange_pixels (pixels_s + 2*(width/2)*(height/2),
			  pixels_d + width/2,
			  width/2, height/2,
			  row_stride);
	rearrange_pixels (pixels_s + 3*(width/2)*(height/2),
			  pixels_d + (height/2)*row_stride + width/2,
			  width/2, height/2,
			  row_stride);
    }

    return pixels_d;
}


static guchar *
unpack_rec_RGB_555 (guint16 *pixels, guint bytes_len, guint byte_order,
		    gint width, gint height)
{
	guchar *result;
	guint16 *use_pixels;
	gboolean free_use_pixels = FALSE;
	guint16 *pixels_arranged;

	g_return_val_if_fail (bytes_len < 2*(G_MAXUINT/3), NULL);
	g_return_val_if_fail (2*width*height < G_MAXUINT, NULL);
	g_return_val_if_fail (width==height, NULL);

	if (2*width*height > bytes_len)
	{
	    use_pixels = g_malloc0 (2*width*height);
	    g_memmove (use_pixels, pixels, bytes_len);
	    free_use_pixels = TRUE;
	}
	else
	{
	    use_pixels = pixels;
	}

	pixels_arranged = rearrange_pixels (use_pixels, NULL,
					    width, height, width);

	if (pixels_arranged == NULL)
	{
	    return NULL;
	}

	result = unpack_RGB_555 (pixels_arranged, bytes_len, byte_order);

	g_free (pixels_arranged);
	if (free_use_pixels)
	{
	    g_free (use_pixels);
	}

	return result;
}


#if DEBUG_ARTWORK
static guchar *
unpack_experimental (guint16 *pixels, guint bytes_len, guint byte_order,
		     gint width, gint height)
{
	guchar *result;
	guint i;
	guint32 *rpixels;

	g_return_val_if_fail (bytes_len < (G_MAXUINT/3), NULL);

	result = g_malloc ((bytes_len/4) * 3);

	rpixels = (guint32 *)pixels;

	for (i = 0; i < bytes_len/4; i++) {
		guint32 cur_pixel;
		/* FIXME: endianness */
		cur_pixel = get_gint32 (rpixels[i], byte_order);
printf ("%8x\n", cur_pixel);
		/* Unpack pixels */
		result[3*i] = (cur_pixel & RED_MASK_888) >> RED_SHIFT_888;
		result[3*i+1] = (cur_pixel & GREEN_MASK_888) >> GREEN_SHIFT_888;
		result[3*i+2] = (cur_pixel & BLUE_MASK_888) >> BLUE_SHIFT_888;

		/* Normalize color values so that they use a [0..255] range */
		/* (not really necessary for 888 encoding) */
/* 		result[3*i] <<= (8 - RED_BITS_888); */
/* 		result[3*i+1] <<= (8 - GREEN_BITS_888); */
/* 		result[3*i+2] <<= (8 - BLUE_BITS_888); */
	}

	return result;
}
#endif


/* limit8bit() and unpack_UYVY() adapted from imgconvert.c from the
 * GPixPod project (www.gpixpod.org) */
static gint limit8bit (float x)
{
    if(x >= 255)
    {
	return 255;
    }
    if(x <= 0)
    {
	return 0;
    }
    return x;
}

/* swapping U and V planes this unpacks YV12 */
static guchar *
unpack_I420 (guchar *yuvdata, gint bytes_len, guint byte_order,
		gint width, gint height)
{
	gint imgsize = width*3*height;
	gint yuvdim = width*height;
	guchar* rgbdata;
	gint row, col;
	gint z = 0;
	gint h = 0;
	gint y, u, v;
	gint ustart = yuvdim;
	gint vstart = yuvdim + yuvdim / 4;

	g_return_val_if_fail (bytes_len < 2*(G_MAXUINT/3), NULL);
	g_return_val_if_fail (width * height * 2 == bytes_len, NULL);

	rgbdata = g_malloc(imgsize);

	/* FIXME could be faster */
	while(h < yuvdim){
		y = yuvdata[h];

		row = h / width;
		col = h % width;

		u = yuvdata[ustart + (row/2)*(width/2) + col/2];
		v = yuvdata[vstart + (row/2)*(width/2) + col/2];

		rgbdata[z] = limit8bit((y-16)*1.164 + (v-128)*1.596);
		rgbdata[z+1] = limit8bit((y-16)*1.164 - (v-128)*0.813 - (u-128)*0.391);
		rgbdata[z+2] = limit8bit((y-16)*1.164 + (u-128)*2.018);

		z+=3;
		h++;
	}
	return rgbdata;
}

static guchar *
unpack_UYVY (guchar *yuvdata, gint bytes_len, guint byte_order,
	     gint width, gint height)
{
    gint imgsize = width*3*height;
    guchar* rgbdata;
    gint halfimgsize = imgsize/2;
    gint halfyuv = halfimgsize/3*2;
    gint x = 0;
    gint z = 0;
    gint z2 = 0;
    gint u0, y0, v0, y1, u1, y2, v1, y3;
    gint h = 0;

    g_return_val_if_fail (bytes_len < 2*(G_MAXUINT/3), NULL);
/*     printf ("w=%d h=%d s=%d\n", width, height, bytes_len); */
    g_return_val_if_fail (width * height * 2 == bytes_len, NULL);

    rgbdata =  g_malloc(imgsize);

    while(h < height)
    {
	gint w = 0;
	if((h % 2) == 0)
	{
	    while(w < width)
	    {
		u0 = yuvdata[z];
		y0 = yuvdata[z+1];
		v0 = yuvdata[z+2];
		y1 = yuvdata[z+3];
		rgbdata[x] = limit8bit((y0-16)*1.164 + (v0-128)*1.596);/*R0*/
		rgbdata[x+1] = limit8bit((y0-16)*1.164 - (v0-128)*0.813 - (u0-128)*0.391);/*G0*/
		rgbdata[x+2] = limit8bit((y0-16)*1.164 + (u0-128)*2.018);/*B0*/
		rgbdata[x+3] = limit8bit((y0-16)*1.164 + (v0-128)*1.596);/*R1*/
		rgbdata[x+4] = limit8bit((y1-16)*1.164 - (v0-128)*0.813 - (u0-128)*0.391);/*G1*/
		rgbdata[x+5] = limit8bit((y1-16)*1.164 + (u0-128)*2.018);/*B1*/
		w += 2;
		z += 4;
		x += 6;
	    }
	}
	else
	{
	    while(w < width)
	    {
		u1 = yuvdata[halfyuv+z2];
		y2 = yuvdata[halfyuv+z2+1];
		v1 = yuvdata[halfyuv+z2+2];
		y3 = yuvdata[halfyuv+z2+3];
		rgbdata[x] = limit8bit((y2-16)*1.164 + (v1-128)*1.596);
		rgbdata[x+1] = limit8bit((y2-16)*1.164 - (v1-128)*0.813 - (u1-128)*0.391);
		rgbdata[x+2] = limit8bit((y2-16)*1.164 + (u1-128)*2.018);
		rgbdata[x+3] = limit8bit((y2-16)*1.164 + (v1-128)*1.596);
		rgbdata[x+4] = limit8bit((y3-16)*1.164 - (v1-128)*0.813 - (u1-128)*0.391);
		rgbdata[x+5] = limit8bit((y3-16)*1.164 + (u1-128)*2.018);
		w += 2;
		z2 += 4;
		x += 6;
	    }
	}
	h++;
    }
    return rgbdata;
}

static guchar *
get_pixel_data (Itdb_Device *device, Itdb_Thumb_Ipod_Item *thumb)
{
	gchar *filename = NULL;
	guchar *result = NULL;
	FILE *f = NULL;
	gint res;

	g_return_val_if_fail (thumb, NULL);
	g_return_val_if_fail (thumb->filename, NULL);

	/* thumb->size is read as a guint32 from the iPod, so no overflow
	 * can occur here
	 */
	result = g_malloc (thumb->size);

	filename = itdb_thumb_ipod_get_filename (device, thumb);

	if (!filename)
	{
	    g_print (_("Could not find on iPod: '%s'\n"),
		     thumb->filename);
	    goto error;
	}

	f = fopen (filename, "r");
	if (f == NULL) {
		g_print ("Failed to open %s: %s\n",
			 filename, strerror (errno));
		goto error;
	}

	res = fseek (f, thumb->offset, SEEK_SET);
	if (res != 0) {
		g_print ("Seek to %d failed on %s: %s\n",
			 thumb->offset, thumb->filename, strerror (errno));
		goto error;
	}

	res = fread (result, thumb->size, 1, f);
	if (res != 1) {
		g_print ("Failed to read %u bytes from %s: %s\n",
			 thumb->size, thumb->filename, strerror (errno));
		goto error;
	}

	goto cleanup;

  error:
	g_free (result);
	result = NULL;
  cleanup:
	if (f != NULL) {
		fclose (f);
	}
	g_free (filename);

	return result;
}

static guchar *
itdb_thumb_get_rgb_data (Itdb_Device *device, Itdb_Thumb_Ipod_Item *item)
{
#if 0
    #include <unistd.h>
    #include <fcntl.h>
    static gint i=0;
    int fd;
    gchar *name;
#endif
	void *pixels_raw;
	guchar *pixels=NULL;

	g_return_val_if_fail (device, NULL);
	g_return_val_if_fail (item, NULL);
	g_return_val_if_fail (item->size != 0, NULL);

        if (item->format == NULL) {
            return NULL;
        }

	pixels_raw = get_pixel_data (device, item);

#if 0
    name = g_strdup_printf ("thumb_%03d.raw", i++);
    fd = creat (name, S_IRWXU|S_IRWXG|S_IRWXO);
    write (fd, pixels_raw, thumb->size);
    close (fd);
    g_free (name);
#endif
	if (pixels_raw == NULL) {
		return NULL;
	}

	switch (item->format->format)
	{
	case THUMB_FORMAT_RGB565_LE_90:
	case THUMB_FORMAT_RGB565_BE_90:
	    /* FIXME: actually the previous two might require
	       different treatment (used on iPod Photo for the full
	       screen photo thumbnail) */
	case THUMB_FORMAT_RGB565_LE:
	case THUMB_FORMAT_RGB565_BE:
	    pixels = unpack_RGB_565 (pixels_raw, item->size,
				     itdb_thumb_get_byteorder (item->format->format));
	    break;
	case THUMB_FORMAT_RGB555_LE_90:
	case THUMB_FORMAT_RGB555_BE_90:
	    /* FIXME: actually the previous two might require
	       different treatment (used on iPod Photo for the full
	       screen photo thumbnail) */
	case THUMB_FORMAT_RGB555_LE:
	case THUMB_FORMAT_RGB555_BE:
	    pixels = unpack_RGB_555 (pixels_raw, item->size,
				     itdb_thumb_get_byteorder (item->format->format));
	    break;
	case THUMB_FORMAT_RGB888_LE_90:
	case THUMB_FORMAT_RGB888_BE_90:
	    /* FIXME: actually the previous two might require
	       different treatment */
	case THUMB_FORMAT_RGB888_LE:
	case THUMB_FORMAT_RGB888_BE:
	    pixels = unpack_RGB_888 (pixels_raw, item->size,
				     itdb_thumb_get_byteorder (item->format->format));
	    break;
	case THUMB_FORMAT_REC_RGB555_LE_90:
	case THUMB_FORMAT_REC_RGB555_BE_90:
	    /* FIXME: actually the previous two might require
	       different treatment (used on iPod Photo for the full
	       screen photo thumbnail) */
	case THUMB_FORMAT_REC_RGB555_LE:
	case THUMB_FORMAT_REC_RGB555_BE:
	    pixels = unpack_rec_RGB_555 (pixels_raw, item->size,
					 itdb_thumb_get_byteorder (item->format->format),
					 item->format->width, item->format->height);
	    break;
	case THUMB_FORMAT_EXPERIMENTAL_LE:
	case THUMB_FORMAT_EXPERIMENTAL_BE:
#if DEBUG_ARTWORK
	    pixels = unpack_experimental (pixels_raw, item->size,
					  itdb_thumb_get_byteorder (item->format->format),
					  item->format->width, item->format->height);
	    break;
#endif
	case THUMB_FORMAT_UYVY_LE:
	case THUMB_FORMAT_UYVY_BE:
	    pixels = unpack_UYVY (pixels_raw, item->size,
				  itdb_thumb_get_byteorder (item->format->format),
				  item->format->width, item->format->height);
	    break;
	case THUMB_FORMAT_I420_LE:
	case THUMB_FORMAT_I420_BE:
	    pixels = unpack_I420 (pixels_raw, item->size,
				  itdb_thumb_get_byteorder (item->format->format),
				  item->format->width, item->format->height);
	    break;
	}
	g_free (pixels_raw);

	return pixels;

}

static guint get_aligned_width (const Itdb_ArtworkFormat *img_info,
                                gsize pixel_size)
{
    guint width;
    guint alignment = img_info->row_bytes_alignment/pixel_size;

    if (alignment * pixel_size != img_info->row_bytes_alignment) {
        g_warning ("RowBytesAlignment (%d) not a multiple of pixel size (%"G_GSIZE_FORMAT")",
                   img_info->row_bytes_alignment, pixel_size);
    }

    width = img_info->width;
    if ((alignment != 0) && ((img_info->width % alignment) != 0)) {
        width += alignment - (img_info->width % alignment);
    }
    return width;
}

gpointer itdb_thumb_ipod_item_to_pixbuf (Itdb_Device *device,
                                         Itdb_Thumb_Ipod_Item *item)
{
	/* pixbuf is already on the iPod -> read from there */
	GdkPixbuf *pixbuf_full;
	GdkPixbuf *pixbuf_sub;
        GdkPixbuf *pixbuf;
	gint pad_x = item->horizontal_padding;
	gint pad_y = item->vertical_padding;
	gint width = item->width;
	gint height = item->height;
	guint rowstride;
        const Itdb_ArtworkFormat *img_info = item->format;
        guchar *pixels;

/*	printf ("hp%d vp%d w%d h%d\n",
	       pad_x, pad_y, width, height);*/
        if (item->format == NULL) {
	    g_warning (_("Unable to retrieve thumbnail (appears to be on iPod, but no image info available): filename: '%s'\n"),
		       item->filename);
            return NULL;
        }
	pixels = itdb_thumb_get_rgb_data (device, item);
	if (pixels == NULL)
	{
	    return NULL;
	}

	/* FIXME: this is broken for non-16bpp image formats :-/ */
	rowstride = get_aligned_width (img_info, sizeof(guint16))*3;
	pixbuf_full =
	    gdk_pixbuf_new_from_data (pixels,
				      GDK_COLORSPACE_RGB,
				      FALSE, 8,
				      img_info->width, img_info->height,
				      rowstride,
				      (GdkPixbufDestroyNotify)g_free,
				      NULL);

	/* !! do not g_free(pixels) here: it will be freed when doing a
	 * g_object_unref() on the GdkPixbuf !! */

	/* Remove padding from the pixmap and/or cut the pixmap to the
	   right size. */

	/* Negative offsets indicate that part of the image was cut
	   off at the left/top. thumb->width/height include that part
	   of the image. Positive offsets indicate that part of the
	   thumbnail are padded in black. thumb->width/height also
	   include that part of the image -> reduce width and height
	   by the absolute value of the padding */
	width = width - abs (pad_x);
	height = height - abs (pad_y);
	/* And throw out "negative" padding */
	if (pad_x < 0)		pad_x = 0;
	if (pad_y < 0)		pad_y = 0;
	/* Width/height might still be larger than
	   img_info->width/height, indicating that part of the image
	   was cut off at the right/bottom (similar to negative
	   padding above). Adjust width/height accordingly. */
	if (pad_x + width > img_info->width)
	    width = img_info->width - pad_x;
	if (pad_y + height > img_info->height)
	    height = img_info->height - pad_y;

#if DEBUG_ARTWORK
 	printf ("px=%2d py=%2d x=%3d y=%3d\n", pad_x, pad_y, width, height);
#endif

	pixbuf_sub = gdk_pixbuf_new_subpixbuf (pixbuf_full,
					       pad_x, pad_y,
					       width, height);
	pixbuf = gdk_pixbuf_copy (pixbuf_sub);
	g_object_unref (pixbuf_full);
	g_object_unref (pixbuf_sub);

        return pixbuf;
}
#else
gpointer itdb_thumb_ipod_item_to_pixbuf (Itdb_Device *device,
                                         Itdb_Thumb_Ipod_Item *item)
{
    return NULL;
}
#endif

/**
 * itdb_artwork_get_pixbuf:
 * @artwork:    an #Itdb_Artwork
 * @device:     an #Itdb_Device
 * @width:      width of the pixbuf to retrieve, -1 for the biggest
 *              possible size and 0 for the smallest possible size (with no
 *              scaling)
 * @height:     height of the pixbuf to retrieve, -1 for the biggest possible
 *              size and 0 for the smallest possible size (with no scaling)
 *
 * Returns a #GdkPixbuf representing the thumbnail stored in @artwork
 * scaling it if appropriate. If either height or width is -1, then the
 * biggest unscaled thumbnail available will be returned
 *
 * Returns: a #GdkPixbuf that must be unreffed when no longer used, NULL
 * if no artwork could be found or if libgpod is compiled without GdkPixbuf
 * support
 *
 * Since: 0.7.0
 */
gpointer itdb_artwork_get_pixbuf (Itdb_Device *device, Itdb_Artwork *artwork,
                                  gint width, gint height)
{
    g_return_val_if_fail (artwork != NULL, NULL);
    if (artwork->thumbnail == NULL) {
        return NULL;
    }
    return itdb_thumb_to_pixbuf_at_size (device, artwork->thumbnail,
                                         width, height);
}