/* -*- Mode: C; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 3 -*- */ /* * GImageView * Copyright (C) 2001-2002 Takuro Ashie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id: png_loader.c,v 1.10 2003/06/14 03:39:50 makeinu Exp $ */ #include #include #include #include #include #include "png_loader.h" #include "gimv_plugin.h" typedef struct MyPngReadContext_Tag { GimvIO *gio; gulong bytes_read; } MyPngReadContext; static GimvImageLoaderPlugin gimv_png_loader[] = { { if_version: GIMV_IMAGE_LOADER_IF_VERSION, id: "PNG", priority_hint: GIMV_IMAGE_LOADER_PRIORITY_CAN_CANCEL, check_type: NULL, get_info: NULL, loader: gimv_png_load, } }; static const gchar *png_extensions[] = { "png", }; static GimvMimeTypeEntry png_mime_types[] = { { mime_type: "image/png", description: "PNG Image", extensions: png_extensions, extensions_len: sizeof (png_extensions) / sizeof (gchar *), icon: NULL, }, }; GIMV_PLUGIN_GET_IMPL(gimv_png_loader, GIMV_PLUGIN_IMAGE_LOADER) GIMV_PLUGIN_GET_MIME_TYPE(png_mime_types) GimvPluginInfo gimv_plugin_info = { if_version: GIMV_PLUGIN_IF_VERSION, name: N_("PNG Image Loader"), version: "0.1.1", author: N_("Takuro Ashie"), description: NULL, get_implement: gimv_plugin_get_impl, get_mime_type: gimv_plugin_get_mime_type, get_prefs_ui: NULL, }; static void my_png_read (png_structp png_ptr, png_bytep data, png_uint_32 len) { guint bytes_read; MyPngReadContext *context = png_get_io_ptr(png_ptr); gimv_io_read (context->gio, data, len, &bytes_read); context->bytes_read += bytes_read; } static gboolean gimv_png_check_type (GimvIO *gio) { guchar buf[16]; size_t bytes_read; long pos; g_return_val_if_fail (gio, FALSE); gimv_io_tell (gio, &pos); gimv_io_read (gio, buf, 8, &bytes_read); if (bytes_read != 8) return FALSE; if (!(buf[0] == 0x89 && buf[1] == 'P' && buf[2] == 'N' && buf[3] == 'G')) return FALSE; if (buf[4] != 0x0d || buf[5] != 0x0a || buf[6] != 0x1a || buf[7] != 0x0a) return FALSE; gimv_io_seek (gio, pos, SEEK_SET); return TRUE; } static gboolean setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr, gint *width_p, gint *height_p, gint *color_type_p, gint *num_passes) { png_uint_32 width, height; int bit_depth, color_type, interlace_type, compression_type, filter_type; int channels; g_return_val_if_fail (png_read_ptr && png_info_ptr, FALSE); g_return_val_if_fail (width_p && height_p, FALSE); g_return_val_if_fail (num_passes, FALSE); png_get_IHDR (png_read_ptr, png_info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_type); *num_passes = 1; if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8) { /* Convert indexed images to RGB */ png_set_expand (png_read_ptr); } else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { /* Convert grayscale to RGB */ png_set_expand (png_read_ptr); } else if (png_get_valid (png_read_ptr, png_info_ptr, PNG_INFO_tRNS)) { /* If we have transparency header, convert it to alpha channel */ png_set_expand(png_read_ptr); } else if (bit_depth < 8) { /* If we have < 8 scale it up to 8 */ png_set_expand(png_read_ptr); } /* If we are 16-bit, convert to 8-bit */ if (bit_depth == 16) { png_set_strip_16(png_read_ptr); } /* If gray scale, convert to RGB */ if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_read_ptr); } /* If interlaced, handle that */ if (interlace_type != PNG_INTERLACE_NONE) { *num_passes = png_set_interlace_handling(png_read_ptr); } /* If alpha channel is not supported by GImageView, strip it. */ if (!gimv_image_can_alpha(NULL) && (color_type & PNG_COLOR_MASK_ALPHA)) { png_set_strip_alpha(png_read_ptr); } /* Update the info the reflect our transformations */ png_read_update_info(png_read_ptr, png_info_ptr); png_get_IHDR (png_read_ptr, png_info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_type); *width_p = (gint) width; *height_p = (gint) height; *color_type_p = color_type; if (bit_depth != 8) { g_warning("Bits per channel of transformed PNG is %d, not 8.", bit_depth); return FALSE; } if (!(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) { g_warning("Transformed PNG not RGB or RGBA."); return FALSE; } channels = png_get_channels(png_read_ptr, png_info_ptr); if (!(channels == 3 || channels == 4) ) { g_warning("Transformed PNG has %d channels, must be 3 or 4.", channels); return FALSE; } return TRUE; } GimvImage * gimv_png_load (GimvImageLoader *loader, gpointer data) { MyPngReadContext context; GimvIO *gio; GimvImage *image; png_structp png_ptr; png_infop png_info; png_text *text; guchar *pixels; gboolean success, alpha; gint width, height, ctype, bpp, num_passes, i; gint bytes_count = 0, bytes_count_tmp = 0, step = 65536; gint n_text = 0; /* checks */ g_return_val_if_fail (GIMV_IS_IMAGE_LOADER (loader), FALSE); gio = gimv_image_loader_get_gio (loader); if (!gio) return NULL; if (!gimv_png_check_type (gio)) return NULL; /* setup libpng */ png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) return NULL; png_info = png_create_info_struct (png_ptr); if (!png_info) { png_destroy_read_struct (&png_ptr, NULL, NULL); return NULL; } if (setjmp (png_ptr->jmpbuf)) goto ERROR; context.gio = gio; context.bytes_read = 0; png_set_read_fn (png_ptr, &context, (png_rw_ptr) my_png_read); /* read header */ png_read_info (png_ptr, png_info); success = setup_png_transformations (png_ptr, png_info, &width, &height, &ctype, &num_passes); if (!success) goto ERROR; if (!gimv_image_loader_progress_update (loader)) goto ERROR; alpha = ctype & PNG_COLOR_MASK_ALPHA; bpp = alpha ? 4:3; /* read image */ pixels = g_malloc0 (width * height * bpp); for (i = 0; i < num_passes; i++) { guchar *buf = pixels; gint j; for (j = 0; j < height; j++) { png_read_row (png_ptr, buf, NULL); buf += width * bpp; bytes_count_tmp = context.bytes_read / step; if (bytes_count_tmp > bytes_count) { bytes_count = bytes_count_tmp; if (!gimv_image_loader_progress_update (loader)) goto ERROR1; } } } image = gimv_image_create_from_data (pixels, width, height, alpha); /* get comments */ png_get_text (png_ptr, png_info, &text, &n_text); for (i = 0; i < n_text; i++) { gimv_image_add_comment (image, text[i].key, text[i].text); } png_destroy_read_struct (&png_ptr, &png_info, NULL); return image; ERROR1: g_free (pixels); ERROR: png_destroy_read_struct (&png_ptr, &png_info, NULL); return NULL; }